summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-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.java310
-rw-r--r--core/java/android/app/Activity.java398
-rw-r--r--core/java/android/app/ActivityManager.java122
-rw-r--r--core/java/android/app/ActivityManagerNative.java267
-rw-r--r--core/java/android/app/ActivityOptions.java314
-rw-r--r--core/java/android/app/ActivityThread.java69
-rw-r--r--core/java/android/app/ActivityTransitionCoordinator.java842
-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.java31
-rw-r--r--core/java/android/app/ContextImpl.java111
-rw-r--r--core/java/android/app/Dialog.java28
-rw-r--r--core/java/android/app/EnterTransitionCoordinator.java287
-rw-r--r--core/java/android/app/ExitTransitionCoordinator.java171
-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.java43
-rw-r--r--core/java/android/app/IApplicationThread.java14
-rw-r--r--core/java/android/app/IBackupAgent.aidl20
-rw-r--r--core/java/android/app/INotificationManager.aidl17
-rw-r--r--core/java/android/app/IProcessObserver.aidl2
-rw-r--r--core/java/android/app/IUiAutomationConnection.aidl8
-rw-r--r--core/java/android/app/Instrumentation.java13
-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.java2
-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/VoiceInteractor.java275
-rw-r--r--core/java/android/app/WallpaperManager.java93
-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.java370
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl17
-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/task/ITaskCallback.aidl53
-rw-r--r--core/java/android/app/task/ITaskService.aidl35
-rw-r--r--core/java/android/app/task/TaskParams.aidl19
-rw-r--r--core/java/android/app/task/TaskParams.java86
-rw-r--r--core/java/android/app/task/TaskService.java260
-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.java21
-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.java179
-rw-r--r--core/java/android/content/Context.java327
-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.java233
-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/Task.java400
-rw-r--r--core/java/android/content/TaskManager.java66
-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.aidl40
-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.aidl29
-rw-r--r--core/java/android/content/pm/LauncherActivityInfo.java167
-rw-r--r--core/java/android/content/pm/LauncherApps.java319
-rw-r--r--core/java/android/content/pm/PackageInfo.java21
-rw-r--r--core/java/android/content/pm/PackageManager.java174
-rw-r--r--core/java/android/content/pm/PackageParser.java85
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java25
-rw-r--r--core/java/android/content/pm/Signature.java40
-rw-r--r--core/java/android/content/pm/UserInfo.java40
-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.java677
-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.java1347
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java42
-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.java2123
-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.java394
-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/hdmi/HdmiCec.java8
-rw-r--r--core/java/android/hardware/hdmi/HdmiCecDeviceInfo.java168
-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/GeofenceHardware.java4
-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.java53
-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/INetworkScoreCache.aidl43
-rw-r--r--core/java/android/net/INetworkScoreService.aidl61
-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.java15
-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.java128
-rw-r--r--core/java/android/net/NetworkScoreManager.java206
-rw-r--r--core/java/android/net/NetworkScorerAppManager.java164
-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.java86
-rw-r--r--core/java/android/net/ProxyProperties.java11
-rw-r--r--core/java/android/net/RssiCurve.java177
-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.java117
-rw-r--r--core/java/android/net/SntpClient.java1
-rw-r--r--core/java/android/net/Uri.java28
-rw-r--r--core/java/android/net/WifiKey.java122
-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/INfcCardEmulation.aidl4
-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/cardemulation/AidGroup.aidl19
-rw-r--r--core/java/android/nfc/cardemulation/AidGroup.java165
-rw-r--r--core/java/android/nfc/cardemulation/ApduServiceInfo.java233
-rw-r--r--core/java/android/nfc/cardemulation/CardEmulation.java107
-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.java2268
-rw-r--r--core/java/android/os/Broadcaster.java8
-rw-r--r--core/java/android/os/Build.java72
-rw-r--r--core/java/android/os/Bundle.java771
-rw-r--r--core/java/android/os/CommonBundle.java1384
-rw-r--r--core/java/android/os/CommonClock.java8
-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.java183
-rw-r--r--core/java/android/os/FileObserver.java3
-rw-r--r--core/java/android/os/FileUtils.java15
-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.aidl3
-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.java74
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java4
-rw-r--r--core/java/android/os/PersistableBundle.java555
-rw-r--r--core/java/android/os/PowerManager.java45
-rw-r--r--core/java/android/os/RecoverySystem.java1
-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.java172
-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/PreferenceFragment.java25
-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.java31
-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/DocumentsContract.java154
-rw-r--r--core/java/android/provider/DocumentsProvider.java181
-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.java232
-rw-r--r--core/java/android/provider/TvContract.java596
-rw-r--r--core/java/android/service/notification/Condition.aidl20
-rw-r--r--core/java/android/service/notification/Condition.java176
-rw-r--r--core/java/android/service/notification/ConditionProviderService.java161
-rw-r--r--core/java/android/service/notification/IConditionListener.aidl25
-rw-r--r--core/java/android/service/notification/IConditionProvider.aidl28
-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/notification/ZenModeConfig.aidl20
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java312
-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/voice/IVoiceInteractionService.aidl23
-rw-r--r--core/java/android/service/voice/IVoiceInteractionSession.aidl28
-rw-r--r--core/java/android/service/voice/IVoiceInteractionSessionService.aidl28
-rw-r--r--core/java/android/service/voice/VoiceInteractionService.java77
-rw-r--r--core/java/android/service/voice/VoiceInteractionServiceInfo.java125
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java195
-rw-r--r--core/java/android/service/voice/VoiceInteractionSessionService.java82
-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/format/Formatter.java65
-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/ChangeClipBounds.java96
-rw-r--r--core/java/android/transition/ChangeTransform.java163
-rw-r--r--core/java/android/transition/CircularPropagation.java107
-rw-r--r--core/java/android/transition/Explode.java228
-rw-r--r--core/java/android/transition/Fade.java256
-rw-r--r--core/java/android/transition/MatrixClippedDrawable.java300
-rw-r--r--core/java/android/transition/MoveImage.java338
-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.java169
-rw-r--r--core/java/android/transition/Slide.java243
-rw-r--r--core/java/android/transition/Transition.java290
-rw-r--r--core/java/android/transition/TransitionInflater.java71
-rw-r--r--core/java/android/transition/TransitionManager.java9
-rw-r--r--core/java/android/transition/TransitionPropagation.java88
-rw-r--r--core/java/android/transition/TransitionSet.java75
-rw-r--r--core/java/android/transition/Visibility.java205
-rw-r--r--core/java/android/transition/VisibilityPropagation.java112
-rw-r--r--core/java/android/tv/ITvInputClient.aidl31
-rw-r--r--core/java/android/tv/ITvInputManager.aidl49
-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.aidl39
-rw-r--r--core/java/android/tv/ITvInputSessionCallback.aidl28
-rw-r--r--core/java/android/tv/ITvInputSessionWrapper.java179
-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.java742
-rw-r--r--core/java/android/tv/TvInputService.java551
-rw-r--r--core/java/android/tv/TvView.java360
-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.java54
-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.java12
-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.java197
-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.java506
-rw-r--r--core/java/android/view/LayoutInflater.java150
-rw-r--r--core/java/android/view/MotionEvent.java22
-rw-r--r--core/java/android/view/PointerIcon.java15
-rw-r--r--core/java/android/view/RenderNode.java961
-rw-r--r--core/java/android/view/RenderNodeAnimator.java122
-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.java17
-rw-r--r--core/java/android/view/TextureView.java68
-rw-r--r--core/java/android/view/ThreadedRenderer.java319
-rw-r--r--core/java/android/view/View.java2232
-rw-r--r--core/java/android/view/ViewConfiguration.java2
-rw-r--r--core/java/android/view/ViewGroup.java255
-rw-r--r--core/java/android/view/ViewOverlay.java15
-rw-r--r--core/java/android/view/ViewParent.java122
-rw-r--r--core/java/android/view/ViewPropertyAnimator.java151
-rw-r--r--core/java/android/view/ViewRootImpl.java468
-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.java189
-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.java51
-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.java72
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java108
-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/WebSettings.java57
-rw-r--r--core/java/android/webkit/WebView.java142
-rw-r--r--core/java/android/webkit/WebViewClient.java39
-rw-r--r--core/java/android/webkit/WebViewFactory.java2
-rw-r--r--core/java/android/webkit/WebViewProvider.java7
-rw-r--r--core/java/android/widget/AbsListView.java1598
-rw-r--r--core/java/android/widget/AbsSeekBar.java70
-rw-r--r--core/java/android/widget/AbsSpinner.java12
-rw-r--r--core/java/android/widget/AbsoluteLayout.java13
-rw-r--r--core/java/android/widget/ActionMenuPresenter.java (renamed from core/java/com/android/internal/view/menu/ActionMenuPresenter.java)70
-rw-r--r--core/java/android/widget/ActionMenuView.java (renamed from core/java/com/android/internal/view/menu/ActionMenuView.java)134
-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.java37
-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.java17
-rw-r--r--core/java/android/widget/ListView.java357
-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.java21
-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.java103
-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.java239
-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.java97
-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
-rw-r--r--core/java/com/android/internal/app/AlertController.java249
-rw-r--r--core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java1
-rw-r--r--core/java/com/android/internal/app/IAppOpsService.aidl2
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl41
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl31
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractor.aidl33
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractorCallback.aidl31
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractorRequest.aidl24
-rw-r--r--core/java/com/android/internal/app/IntentForwarderActivity.java112
-rw-r--r--core/java/com/android/internal/app/LocalePicker.java2
-rw-r--r--core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java1
-rw-r--r--core/java/com/android/internal/app/MediaRouteControllerDialog.java4
-rw-r--r--core/java/com/android/internal/app/PlatLogoActivity.java5
-rw-r--r--core/java/com/android/internal/app/ProcessStats.java868
-rw-r--r--core/java/com/android/internal/app/ToolbarActionBar.java447
-rw-r--r--core/java/com/android/internal/app/WindowDecorActionBar.java (renamed from core/java/com/android/internal/app/ActionBarImpl.java)109
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetService.aidl1
-rw-r--r--core/java/com/android/internal/backup/LocalTransport.java138
-rw-r--r--core/java/com/android/internal/content/PackageMonitor.java7
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java300
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodUtils.java28
-rw-r--r--core/java/com/android/internal/net/NetworkStatsFactory.java33
-rw-r--r--core/java/com/android/internal/net/VpnConfig.java2
-rw-r--r--core/java/com/android/internal/notification/PeopleNotificationScorer.java227
-rw-r--r--core/java/com/android/internal/os/BatterySipper.java100
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHelper.java838
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java3535
-rw-r--r--core/java/com/android/internal/os/HandlerCaller.java41
-rw-r--r--core/java/com/android/internal/os/SomeArgs.java8
-rw-r--r--core/java/com/android/internal/policy/IKeyguardService.aidl16
-rw-r--r--core/java/com/android/internal/policy/IKeyguardServiceConstants.java41
-rw-r--r--core/java/com/android/internal/preference/YesNoPreference.java12
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl11
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java48
-rw-r--r--core/java/com/android/internal/util/GrowingArrayUtils.java196
-rw-r--r--core/java/com/android/internal/util/ImageUtils.java84
-rw-r--r--core/java/com/android/internal/util/LegacyNotificationUtil.java193
-rw-r--r--core/java/com/android/internal/view/ActionBarPolicy.java2
-rw-r--r--core/java/com/android/internal/view/IInputMethodClient.aidl1
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl5
-rw-r--r--core/java/com/android/internal/view/RotationPolicy.java73
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItem.java2
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItemView.java81
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuItemView.java17
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuItemView.java18
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuPresenter.java1
-rw-r--r--core/java/com/android/internal/view/menu/MenuBuilder.java12
-rw-r--r--core/java/com/android/internal/view/menu/MenuItemImpl.java2
-rw-r--r--core/java/com/android/internal/view/menu/MenuPopupHelper.java1
-rw-r--r--core/java/com/android/internal/widget/AbsActionBarView.java20
-rw-r--r--core/java/com/android/internal/widget/ActionBarContainer.java84
-rw-r--r--core/java/com/android/internal/widget/ActionBarContextView.java19
-rw-r--r--core/java/com/android/internal/widget/ActionBarOverlayLayout.java284
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java11
-rw-r--r--core/java/com/android/internal/widget/AutoScrollHelper.java4
-rw-r--r--core/java/com/android/internal/widget/DialogTitle.java11
-rw-r--r--core/java/com/android/internal/widget/FaceUnlockView.java2
-rw-r--r--core/java/com/android/internal/widget/ILockSettings.aidl1
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java145
-rw-r--r--core/java/com/android/internal/widget/LockPatternView.java23
-rw-r--r--core/java/com/android/internal/widget/PasswordEntryKeyboard.java5
-rw-r--r--core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java2
-rw-r--r--core/java/com/android/internal/widget/PasswordEntryKeyboardView.java11
-rw-r--r--core/java/com/android/internal/widget/PointerLocationView.java11
-rw-r--r--core/java/com/android/internal/widget/RotarySelector.java6
-rw-r--r--core/java/com/android/internal/widget/ScrollingTabContainerView.java1
-rw-r--r--core/java/com/android/internal/widget/SizeAdaptiveLayout.java23
-rw-r--r--core/java/com/android/internal/widget/SlidingTab.java3
-rw-r--r--core/java/com/android/internal/widget/SubtitleView.java41
-rw-r--r--core/java/com/android/internal/widget/TextProgressBar.java9
-rw-r--r--core/java/com/android/internal/widget/WaveView.java4
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/GlowPadView.java5
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java1269
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java1
-rw-r--r--core/java/com/android/server/AppWidgetBackupBridge.java63
-rw-r--r--core/java/com/android/server/SystemService.java32
-rw-r--r--core/java/com/android/server/SystemServiceManager.java52
-rw-r--r--core/java/com/android/server/WidgetBackupProvider.java34
-rw-r--r--core/java/com/android/server/net/BaseNetworkObserver.java2
728 files changed, 64945 insertions, 19373 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..3c3df01 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();
@@ -775,7 +933,93 @@ public abstract class ActionBar {
public void setHomeActionContentDescription(int resId) { }
/**
+ * Enable hiding the action bar on content scroll.
+ *
+ * <p>If enabled, the action bar will scroll out of sight along with a
+ * {@link View#setNestedScrollingEnabled(boolean) nested scrolling child} view's content.
+ * The action bar must be in {@link Window#FEATURE_ACTION_BAR_OVERLAY overlay mode}
+ * to enable hiding on content scroll.</p>
+ *
+ * <p>When partially scrolled off screen the action bar is considered
+ * {@link #hide() hidden}. A call to {@link #show() show} will cause it to return to full view.
+ * </p>
+ * @param hideOnContentScroll true to enable hiding on content scroll.
+ */
+ public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
+ if (hideOnContentScroll) {
+ throw new UnsupportedOperationException("Hide on content scroll is not supported in " +
+ "this action bar configuration.");
+ }
+ }
+
+ /**
+ * Return whether the action bar is configured to scroll out of sight along with
+ * a {@link View#setNestedScrollingEnabled(boolean) nested scrolling child}.
+ *
+ * @return true if hide-on-content-scroll is enabled
+ * @see #setHideOnContentScrollEnabled(boolean)
+ */
+ public boolean isHideOnContentScrollEnabled() {
+ return false;
+ }
+
+ /**
+ * Return the current vertical offset of the action bar.
+ *
+ * <p>The action bar's current hide offset is the distance that the action bar is currently
+ * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's
+ * current measured {@link #getHeight() height} (fully invisible).</p>
+ *
+ * @return The action bar's offset toward its fully hidden state in pixels
+ */
+ public int getHideOffset() {
+ return 0;
+ }
+
+ /**
+ * Set the current hide offset of the action bar.
+ *
+ * <p>The action bar's current hide offset is the distance that the action bar is currently
+ * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's
+ * current measured {@link #getHeight() height} (fully invisible).</p>
+ *
+ * @param offset The action bar's offset toward its fully hidden state in pixels.
+ */
+ public void setHideOffset(int offset) {
+ if (offset != 0) {
+ throw new UnsupportedOperationException("Setting an explicit action bar hide offset " +
+ "is not supported in this action bar configuration.");
+ }
+ }
+
+ /** @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 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 +1052,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 +1208,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 +1254,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 +1291,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 +1305,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..8981c88 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.TransitionManager;
import android.util.ArrayMap;
import android.util.SuperNotCalledException;
-import com.android.internal.app.ActionBarImpl;
+import android.widget.Toolbar;
+import com.android.internal.app.IVoiceInteractor;
+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,9 +724,11 @@ 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 VoiceInteractor mVoiceInteractor;
+
private CharSequence mTitle;
private int mTitleColor = 0;
@@ -763,6 +775,8 @@ public class Activity extends ContextThemeWrapper
private Thread mUiThread;
final Handler mHandler = new Handler();
+ private ActivityOptions mCalledActivityOptions;
+ private EnterTransitionCoordinator mEnterTransitionCoordinator;
/** Return the intent that started this activity. */
public Intent getIntent() {
@@ -852,6 +866,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 +897,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,18 +1025,21 @@ 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());
}
+ if (mEnterTransitionCoordinator != null) {
+ mEnterTransitionCoordinator.readyToEnter();
+ }
mCalled = true;
}
/**
* 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
@@ -1095,6 +1113,7 @@ public class Activity extends ContextThemeWrapper
protected void onResume() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
getApplication().dispatchActivityResumed(this);
+ mCalledActivityOptions = null;
mCalled = true;
}
@@ -1118,6 +1137,23 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Check whether this activity is running as part of a voice interaction with the user.
+ * If true, it should perform its interaction with the user through the
+ * {@link VoiceInteractor} returned by {@link #getVoiceInteractor}.
+ */
+ public boolean isVoiceInteraction() {
+ return mVoiceInteractor != null;
+ }
+
+ /**
+ * Retrieve the active {@link VoiceInteractor} that the user is going through to
+ * interact with this activity.
+ */
+ public VoiceInteractor getVoiceInteractor() {
+ return mVoiceInteractor;
+ }
+
+ /**
* This is called for activities that set launchMode to "singleTop" in
* their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP}
* flag when calling {@link #startActivity}. In either case, when the
@@ -1347,6 +1383,7 @@ public class Activity extends ContextThemeWrapper
* @see #onSaveInstanceState
* @see #onPause
*/
+ @Nullable
public CharSequence onCreateDescription() {
return null;
}
@@ -1386,6 +1423,10 @@ public class Activity extends ContextThemeWrapper
protected void onStop() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
+ if (mCalledActivityOptions != null) {
+ mCalledActivityOptions.dispatchActivityStopped();
+ mCalledActivityOptions = null;
+ }
getApplication().dispatchActivityStopped(this);
mTranslucentCallback = null;
mCalled = true;
@@ -1551,6 +1592,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 +1672,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 +1685,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 +1933,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 +1978,7 @@ public class Activity extends ContextThemeWrapper
return;
}
- mActionBar = new ActionBarImpl(this);
+ mActionBar = new WindowDecorActionBar(this);
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
mWindow.setDefaultIcon(mActivityInfo.getIconResource());
@@ -1927,7 +1996,7 @@ public class Activity extends ContextThemeWrapper
*/
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
- initActionBar();
+ initWindowDecorActionBar();
}
/**
@@ -1947,7 +2016,7 @@ public class Activity extends ContextThemeWrapper
*/
public void setContentView(View view) {
getWindow().setContentView(view);
- initActionBar();
+ initWindowDecorActionBar();
}
/**
@@ -1963,7 +2032,7 @@ public class Activity extends ContextThemeWrapper
*/
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
- initActionBar();
+ initWindowDecorActionBar();
}
/**
@@ -1975,7 +2044,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 +2089,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 +2169,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 +2327,7 @@ public class Activity extends ContextThemeWrapper
*/
public void onBackPressed() {
if (!mFragments.popBackStackImmediate()) {
- finish();
+ finishWithTransition();
}
}
@@ -2528,6 +2642,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 +2689,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 +2770,7 @@ public class Activity extends ContextThemeWrapper
break;
case Window.FEATURE_ACTION_BAR:
- initActionBar();
+ initWindowDecorActionBar();
mActionBar.dispatchMenuVisibilityChanged(false);
break;
}
@@ -3025,6 +3140,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 +3228,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 +3350,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 +3374,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 +3391,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 +3458,7 @@ public class Activity extends ContextThemeWrapper
* Convenience for calling
* {@link android.view.Window#getLayoutInflater}.
*/
+ @NonNull
public LayoutInflater getLayoutInflater() {
return getWindow().getLayoutInflater();
}
@@ -3348,10 +3466,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 +3505,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(mWindow, null).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 +3531,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 +3547,14 @@ 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);
+ activityOptions.dispatchStartExit();
+ mCalledActivityOptions = activityOptions;
+ }
if (mParent == null) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
@@ -3505,7 +3633,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 +3665,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 +3727,7 @@ public class Activity extends ContextThemeWrapper
*/
@Override
public void startActivity(Intent intent) {
- startActivity(intent, null);
+ this.startActivity(intent, null);
}
/**
@@ -3625,7 +3753,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 +3802,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 +3821,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 +3848,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 +3876,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 +3910,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 +3960,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 +3983,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 +4013,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 +4037,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 +4063,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 +4088,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 +4121,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 +4220,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 +4243,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);
@@ -4203,11 +4334,10 @@ public class Activity extends ContextThemeWrapper
}
/**
- * Call this when your activity is done and should be closed. The
- * ActivityResult is propagated back to whoever launched you via
- * onActivityResult().
+ * Finishes the current activity and specifies whether to remove the task associated with this
+ * activity.
*/
- public void finish() {
+ private void finish(boolean finishTask) {
if (mParent == null) {
int resultCode;
Intent resultData;
@@ -4221,7 +4351,7 @@ public class Activity extends ContextThemeWrapper
resultData.prepareToLeaveProcess();
}
if (ActivityManagerNative.getDefault()
- .finishActivity(mToken, resultCode, resultData)) {
+ .finishActivity(mToken, resultCode, resultData, finishTask)) {
mFinished = true;
}
} catch (RemoteException e) {
@@ -4233,6 +4363,15 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Call this when your activity is done and should be closed. The
+ * ActivityResult is propagated back to whoever launched you via
+ * onActivityResult().
+ */
+ public void finish() {
+ finish(false);
+ }
+
+ /**
* Finish this activity as well as all activities immediately below it
* in the current task that have the same affinity. This is typically
* used when an application can be launched on to another task (such as
@@ -4276,6 +4415,22 @@ 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.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window,
+ * android.app.ActivityOptions.ActivityTransitionListener)
+ */
+ public void finishWithTransition() {
+ if (mEnterTransitionCoordinator != null) {
+ mEnterTransitionCoordinator.startExit();
+ } else {
+ finish();
+ }
+ }
+
+ /**
* Force finish another activity that you had previously started with
* {@link #startActivityForResult}.
*
@@ -4305,7 +4460,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);
@@ -4315,6 +4470,14 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Call this when your activity is done and should be closed and the task should be completely
+ * removed as a part of finishing the Activity.
+ */
+ public void finishAndRemoveTask() {
+ finish(true);
+ }
+
+ /**
* Called when an activity you launched exits, giving you the requestCode
* you started it with, the resultCode it returned, and any additional
* data from it. The <var>resultCode</var> will be
@@ -4366,8 +4529,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 +4557,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 +4579,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 +4651,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 +4697,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 +4739,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 +4779,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 +4845,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 +4903,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 +4939,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 +5198,7 @@ public class Activity extends ContextThemeWrapper
*
* @see ActionMode
*/
+ @Nullable
public ActionMode startActionMode(ActionMode.Callback callback) {
return mWindow.getDecorView().startActionMode(callback);
}
@@ -5005,9 +5214,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 +5358,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)) {
@@ -5170,6 +5381,21 @@ public class Activity extends ContextThemeWrapper
}
}
+ /**
+ * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window,
+ * android.app.ActivityOptions.ActivityTransitionListener)} was used to start an Activity,
+ * the Window will be triggered to enter with a Transition. <code>listener</code> allows
+ * The Activity to listen to events of the entering transition and control the mapping of
+ * shared elements. This requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * @param listener Used to listen to events in the entering transition.
+ */
+ public void setActivityTransitionListener(ActivityOptions.ActivityTransitionListener listener) {
+ if (mEnterTransitionCoordinator != null) {
+ mEnterTransitionCoordinator.setActivityTransitionListener(listener);
+ }
+ }
+
// ------------------ Internal API ------------------
final void setParent(Activity parent) {
@@ -5190,6 +5416,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, 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, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
mFragments.attachActivity(this, mContainer, null);
@@ -5204,7 +5440,7 @@ public class Activity extends ContextThemeWrapper
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
-
+
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
@@ -5217,6 +5453,8 @@ public class Activity extends ContextThemeWrapper
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
+ mVoiceInteractor = voiceInteractor != null
+ ? new VoiceInteractor(this, this, voiceInteractor, Looper.myLooper()) : null;
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
@@ -5227,6 +5465,12 @@ public class Activity extends ContextThemeWrapper
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
+ if (options != null) {
+ ActivityOptions activityOptions = new ActivityOptions(options);
+ if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+ mEnterTransitionCoordinator = activityOptions.createEnterActivityTransition(this);
+ }
+ }
}
/** @hide */
@@ -5240,7 +5484,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 +5641,7 @@ public class Activity extends ContextThemeWrapper
}
}
}
-
+
mStopped = true;
}
mResumed = false;
@@ -5412,7 +5656,7 @@ public class Activity extends ContextThemeWrapper
mLoaderManager.doDestroy();
}
}
-
+
/**
* @hide
*/
@@ -5420,7 +5664,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 +5680,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..9239faf 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -76,6 +76,13 @@ public class ActivityManager {
public static final String META_HOME_ALTERNATE = "android.app.home.alternate";
/**
+ * Result for IActivityManager.startActivity: trying to start an activity under voice
+ * control when that activity does not support the VOICE category.
+ * @hide
+ */
+ public static final int START_NOT_VOICE_COMPATIBLE = -7;
+
+ /**
* Result for IActivityManager.startActivity: an error where the
* start had to be canceled.
* @hide
@@ -155,6 +162,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 +516,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 +555,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 +600,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 +608,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 +980,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
@@ -1570,13 +1627,6 @@ public class ActivityManager {
public int lastTrimLevel;
/**
- * Constant for {@link #importance}: this is a persistent process.
- * Only used when reporting to process observers.
- * @hide
- */
- public static final int IMPORTANCE_PERSISTENT = 50;
-
- /**
* Constant for {@link #importance}: this process is running the
* foreground UI.
*/
@@ -1691,9 +1741,16 @@ public class ActivityManager {
*/
public int importanceReasonImportance;
+ /**
+ * Current process state, as per PROCESS_STATE_* constants.
+ * @hide
+ */
+ public int processState;
+
public RunningAppProcessInfo() {
importance = IMPORTANCE_FOREGROUND;
importanceReasonCode = REASON_UNKNOWN;
+ processState = PROCESS_STATE_IMPORTANT_FOREGROUND;
}
public RunningAppProcessInfo(String pProcessName, int pPid, String pArr[]) {
@@ -1719,6 +1776,7 @@ public class ActivityManager {
dest.writeInt(importanceReasonPid);
ComponentName.writeToParcel(importanceReasonComponent, dest);
dest.writeInt(importanceReasonImportance);
+ dest.writeInt(processState);
}
public void readFromParcel(Parcel source) {
@@ -1734,6 +1792,7 @@ public class ActivityManager {
importanceReasonPid = source.readInt();
importanceReasonComponent = ComponentName.readFromParcel(source);
importanceReasonImportance = source.readInt();
+ processState = source.readInt();
}
public static final Creator<RunningAppProcessInfo> CREATOR =
@@ -1938,7 +1997,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 +2285,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..b1c37de 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -43,9 +43,11 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
+import android.service.voice.IVoiceInteractionSession;
import android.text.TextUtils;
import android.util.Log;
import android.util.Singleton;
+import com.android.internal.app.IVoiceInteractor;
import java.util.ArrayList;
import java.util.List;
@@ -101,9 +103,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) {
}
}
@@ -242,6 +244,33 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case START_VOICE_ACTIVITY_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ String callingPackage = data.readString();
+ int callingPid = data.readInt();
+ int callingUid = data.readInt();
+ Intent intent = Intent.CREATOR.createFromParcel(data);
+ String resolvedType = data.readString();
+ IVoiceInteractionSession session = IVoiceInteractionSession.Stub.asInterface(
+ data.readStrongBinder());
+ IVoiceInteractor interactor = IVoiceInteractor.Stub.asInterface(
+ data.readStrongBinder());
+ int startFlags = data.readInt();
+ String profileFile = data.readString();
+ ParcelFileDescriptor profileFd = data.readInt() != 0
+ ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null;
+ Bundle options = data.readInt() != 0
+ ? Bundle.CREATOR.createFromParcel(data) : null;
+ int userId = data.readInt();
+ int result = startVoiceActivity(callingPackage, callingPid, callingUid,
+ intent, resolvedType, session, interactor, startFlags,
+ profileFile, profileFd, options, userId);
+ reply.writeNoException();
+ reply.writeInt(result);
+ return true;
+ }
+
case START_NEXT_MATCHING_ACTIVITY_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
@@ -263,7 +292,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
if (data.readInt() != 0) {
resultData = Intent.CREATOR.createFromParcel(data);
}
- boolean res = finishActivity(token, resultCode, resultData);
+ boolean finishTask = (data.readInt() != 0);
+ boolean res = finishActivity(token, resultCode, resultData, finishTask);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
@@ -654,6 +684,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 +1283,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 +1727,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 +1866,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 +2102,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);
@@ -2249,6 +2352,42 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
return result;
}
+ public int startVoiceActivity(String callingPackage, int callingPid, int callingUid,
+ Intent intent, String resolvedType, IVoiceInteractionSession session,
+ IVoiceInteractor interactor, int startFlags, String profileFile,
+ ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(callingPackage);
+ data.writeInt(callingPid);
+ data.writeInt(callingUid);
+ intent.writeToParcel(data, 0);
+ data.writeString(resolvedType);
+ data.writeStrongBinder(session.asBinder());
+ data.writeStrongBinder(interactor.asBinder());
+ data.writeInt(startFlags);
+ data.writeString(profileFile);
+ if (profileFd != null) {
+ data.writeInt(1);
+ profileFd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ data.writeInt(0);
+ }
+ if (options != null) {
+ data.writeInt(1);
+ options.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
+ data.writeInt(userId);
+ mRemote.transact(START_VOICE_ACTIVITY_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int result = reply.readInt();
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
public boolean startNextMatchingActivity(IBinder callingActivity,
Intent intent, Bundle options) throws RemoteException {
Parcel data = Parcel.obtain();
@@ -2269,7 +2408,7 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
return result != 0;
}
- public boolean finishActivity(IBinder token, int resultCode, Intent resultData)
+ public boolean finishActivity(IBinder token, int resultCode, Intent resultData, boolean finishTask)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -2282,6 +2421,7 @@ class ActivityManagerProxy implements IActivityManager
} else {
data.writeInt(0);
}
+ data.writeInt(finishTask ? 1 : 0);
mRemote.transact(FINISH_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
boolean res = reply.readInt() != 0;
@@ -2786,6 +2926,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 +3768,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 +4385,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 +4540,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 +4870,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..a49359f 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -22,7 +22,15 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.transition.Transition;
+import android.util.ArrayMap;
+import android.util.Pair;
import android.view.View;
+import android.view.Window;
+
+import java.util.List;
+import java.util.Map;
/**
* Helper class for building an options Bundle that can be used with
@@ -30,6 +38,8 @@ import android.view.View;
* 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,14 @@ 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";
+
/** @hide */
public static final int ANIM_NONE = 0;
/** @hide */
@@ -100,6 +118,8 @@ public class ActivityOptions {
public static final int ANIM_THUMBNAIL_SCALE_UP = 3;
/** @hide */
public static final int ANIM_THUMBNAIL_SCALE_DOWN = 4;
+ /** @hide */
+ public static final int ANIM_SCENE_TRANSITION = 5;
private String mPackageName;
private int mAnimationType = ANIM_NONE;
@@ -111,6 +131,7 @@ public class ActivityOptions {
private int mStartWidth;
private int mStartHeight;
private IRemoteCallback mAnimationStartedListener;
+ private ResultReceiver mExitReceiver;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -156,11 +177,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;
@@ -298,7 +320,60 @@ 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.
+ * The position of <code>sharedElement</code> will be used as the epicenter for the
+ * exit Transition. The position of the shared element in the launched Activity will be the
+ * epicenter of its entering Transition.
+ *
+ * <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 window The window containing shared elements.
+ * @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.
+ * @see android.transition.Transition#setEpicenterCallback(
+ * android.transition.Transition.EpicenterCallback)
+ */
+ public static ActivityOptions makeSceneTransitionAnimation(Window window,
+ View sharedElement, String sharedElementName) {
+ return makeSceneTransitionAnimation(window,
+ new SharedElementMappingListener(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. The position of the first element in the value returned from
+ * {@link android.app.ActivityOptions.ActivityTransitionListener#getSharedElementsMapping()}
+ * will be used as the epicenter for the exit Transition. The position of the associated
+ * shared element in the launched Activity will be the epicenter of its entering Transition.
+ *
+ * <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 window The window containing shared elements.
+ * @param listener The listener to use to monitor activity transition events.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ * @see android.transition.Transition#setEpicenterCallback(
+ * android.transition.Transition.EpicenterCallback)
+ */
+ public static ActivityOptions makeSceneTransitionAnimation(Window window,
+ ActivityTransitionListener listener) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mAnimationType = ANIM_SCENE_TRANSITION;
+ ExitTransitionCoordinator exit = new ExitTransitionCoordinator(window, listener);
+ opts.mExitReceiver = exit;
return opts;
}
@@ -309,23 +384,33 @@ 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:
+ mExitReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER);
+ break;
}
}
@@ -380,6 +465,20 @@ public class ActivityOptions {
}
/** @hide */
+ public void dispatchActivityStopped() {
+ if (mExitReceiver != null) {
+ mExitReceiver.send(ActivityTransitionCoordinator.MSG_ACTIVITY_STOPPED, null);
+ }
+ }
+
+ /** @hide */
+ public void dispatchStartExit() {
+ if (mExitReceiver != null) {
+ mExitReceiver.send(ActivityTransitionCoordinator.MSG_START_EXIT_TRANSITION, null);
+ }
+ }
+
+ /** @hide */
public void abort() {
if (mAnimationStartedListener != null) {
try {
@@ -396,6 +495,15 @@ public class ActivityOptions {
}
}
+ /** @hide */
+ public EnterTransitionCoordinator createEnterActivityTransition(Activity activity) {
+ EnterTransitionCoordinator coordinator = null;
+ if (mAnimationType == ANIM_SCENE_TRANSITION) {
+ coordinator = new EnterTransitionCoordinator(activity, mExitReceiver);
+ }
+ return coordinator;
+ }
+
/**
* Update the current values in this ActivityOptions from those supplied
* in <var>otherOptions</var>. Any values
@@ -405,15 +513,16 @@ public class ActivityOptions {
if (otherOptions.mPackageName != null) {
mPackageName = otherOptions.mPackageName;
}
+ mExitReceiver = 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) {
}
}
@@ -425,9 +534,9 @@ 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) {
}
}
@@ -439,14 +548,20 @@ 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;
break;
+ case ANIM_SCENE_TRANSITION:
+ mAnimationType = otherOptions.mAnimationType;
+ mExitReceiver = otherOptions.mExitReceiver;
+ mThumbnail = null;
+ mAnimationStartedListener = null;
+ break;
}
}
@@ -468,7 +583,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 +599,153 @@ 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 (mExitReceiver != null) {
+ b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mExitReceiver);
+ }
+ 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;
+ }
+
+ /**
+ * Listener provided in
+ * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window,
+ * android.app.ActivityOptions.ActivityTransitionListener)} or in
+ * {@link android.app.Activity#setActivityTransitionListener(
+ * android.app.ActivityOptions.ActivityTransitionListener)} to monitor the Activity transitions.
+ * The events can be used to customize or override Activity Transition behavior.
+ */
+ public static class ActivityTransitionListener {
+ /**
+ * Called when the enter Transition is ready to start, but hasn't started yet. If
+ * {@link android.view.Window#getEnterTransition()} is non-null,
+ * The entering views will be {@link View#INVISIBLE}.
+ */
+ public void onEnterReady() {}
+
+ /**
+ * Called when the remote exiting transition completes.
+ */
+ public void onRemoteExitComplete() {}
+
+ /**
+ * Called when the start state for shared elements is captured on enter.
+ *
+ * @param sharedElementNames The names of the shared elements that were accepted into
+ * the View hierarchy.
+ * @param sharedElements The shared elements that are part of the View hierarchy.
+ * @param sharedElementSnapshots The Views containing snap shots of the shared element
+ * from the launching Window. These elements will not
+ * be part of the scene, but will be positioned relative
+ * to the Window decor View.
+ */
+ public void onCaptureSharedElementStart(List<String> sharedElementNames,
+ List<View> sharedElements, List<View> sharedElementSnapshots) {}
+
+ /**
+ * Called when the end state for shared elements is captured on enter.
+ *
+ * @param sharedElementNames The names of the shared elements that were accepted into
+ * the View hierarchy.
+ * @param sharedElements The shared elements that are part of the View hierarchy.
+ * @param sharedElementSnapshots The Views containing snap shots of the shared element
+ * from the launching Window. These elements will not
+ * be part of the scene, but will be positioned relative
+ * to the Window decor View.
+ */
+ public void onCaptureSharedElementEnd(List<String> sharedElementNames,
+ List<View> sharedElements, List<View> sharedElementSnapshots) {}
+
+ /**
+ * Called when the enter Transition has been started.
+ * @param sharedElementNames The names of shared elements that were transferred.
+ * @param sharedElements The shared elements that were transferred.
+ */
+ public void onStartEnterTransition(List<String> sharedElementNames,
+ List<View> sharedElements) {}
+
+ /**
+ * Called when the exit Transition has been started.
+ * @param sharedElementNames The names of all shared elements that will be transferred.
+ * @param sharedElements All shared elements that will be transferred.
+ */
+ public void onStartExitTransition(List<String> sharedElementNames,
+ List<View> sharedElements) {}
+
+ /**
+ * Called when the exiting shared element transition completes.
+ */
+ public void onSharedElementExitTransitionComplete() {}
+
+ /**
+ * Called on exit when the shared element has been transferred.
+ * @param sharedElementNames The names of all shared elements that were transferred.
+ * @param sharedElements All shared elements that will were transferred.
+ */
+ public void onSharedElementTransferred(List<String> sharedElementNames,
+ List<View> sharedElements) {}
+
+ /**
+ * Called when the exit transition has completed.
+ */
+ public void onExitTransitionComplete() {}
+
+ /**
+ * Returns a mapping from a View in the View hierarchy to the shared element name used
+ * in the call. This is called twice -- once when the view is
+ * entering and again when it exits. A null return value indicates that the
+ * View hierachy can be trusted without any remapping.
+ * @return A map from a View in the hierarchy to the shared element name used in the
+ * call.
+ */
+ public Pair<View, String>[] getSharedElementsMapping() { return null; }
+
+ /**
+ * Returns <code>true</code> if the ActivityTransitionListener will handle removing
+ * rejected shared elements from the scene. If <code>false</code> is returned, a default
+ * animation will be used to remove the rejected shared elements from the scene.
+ *
+ * @param rejectedSharedElements Views containing visual information of shared elements
+ * that are not part of the entering scene. These Views
+ * are positioned relative to the Window decor View.
+ * @return <code>false</code> if the default animation should be used to remove the
+ * rejected shared elements from the scene or <code>true</code> if the listener provides
+ * custom handling.
+ */
+ public boolean handleRejectedSharedElements(List<View> rejectedSharedElements) {
+ return false;
+ }
+ }
+
+ private static class SharedElementMappingListener extends ActivityTransitionListener {
+ Pair<View, String>[] mSharedElementsMapping = new Pair[1];
+
+ public SharedElementMappingListener(View view, String name) {
+ mSharedElementsMapping[0] = Pair.create(view, name);
+ }
+
+ @Override
+ public Pair<View, String>[] getSharedElementsMapping() {
+ return mSharedElementsMapping;
+ }
+ }
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7f8dbba..7dc21b4 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;
@@ -90,6 +94,7 @@ import android.view.WindowManagerGlobal;
import android.renderscript.RenderScript;
import android.security.AndroidKeyStoreProvider;
+import com.android.internal.app.IVoiceInteractor;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SamplingProfilerIntegration;
@@ -261,6 +266,7 @@ public final class ActivityThread {
IBinder token;
int ident;
Intent intent;
+ IVoiceInteractor voiceInteractor;
Bundle state;
Activity activity;
Window window;
@@ -289,6 +295,7 @@ public final class ActivityThread {
boolean isForward;
int pendingConfigChanges;
boolean onlyLocalRequest;
+ Bundle activityOptions;
View mPendingRemoveWindow;
WindowManager mPendingRemoveWindowManager;
@@ -581,9 +588,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) {
@@ -597,9 +605,11 @@ public final class ActivityThread {
// activity itself back to the activity manager. (matters more with ipc)
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
+ IVoiceInteractor voiceInteractor,
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);
@@ -608,6 +618,7 @@ public final class ActivityThread {
r.token = token;
r.ident = ident;
r.intent = intent;
+ r.voiceInteractor = voiceInteractor;
r.activityInfo = info;
r.compatInfo = compatInfo;
r.state = state;
@@ -621,6 +632,7 @@ public final class ActivityThread {
r.profileFile = profileName;
r.profileFd = profileFd;
r.autoStopProfiler = autoStopProfiler;
+ r.activityOptions = resumeArgs;
updatePendingConfiguration(curConfig);
@@ -1244,7 +1256,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 +1302,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 +1596,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 +2089,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 +2142,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 +2201,8 @@ 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,
+ r.voiceInteractor);
if (customIntent != null) {
activity.mIntent = customIntent;
@@ -2297,12 +2312,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) {
@@ -2352,7 +2368,7 @@ public final class ActivityThread {
// manager to stop us.
try {
ActivityManagerNative.getDefault()
- .finishActivity(r.token, Activity.RESULT_CANCELED, null);
+ .finishActivity(r.token, Activity.RESULT_CANCELED, null, false);
} catch (RemoteException ex) {
// Ignore
}
@@ -2861,12 +2877,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) {
@@ -2972,7 +2989,7 @@ public final class ActivityThread {
// just end this activity.
try {
ActivityManagerNative.getDefault()
- .finishActivity(token, Activity.RESULT_CANCELED, null);
+ .finishActivity(token, Activity.RESULT_CANCELED, null, false);
} catch (RemoteException ex) {
}
}
@@ -2991,11 +3008,19 @@ 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);
+ Configuration config = res.getConfiguration();
+ boolean useAlternateRecents = (config.smallestScreenWidthDp < 600);
+ if (useAlternateRecents) {
+ 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 +3812,7 @@ public final class ActivityThread {
}
}
r.startsNotResumed = tmp.startsNotResumed;
+ r.activityOptions = null;
handleLaunchActivity(r, currentIntent);
}
@@ -3964,6 +3990,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/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
new file mode 100644
index 0000000..3c1455b
--- /dev/null
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -0,0 +1,842 @@
+/*
+ * 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.transition.Transition;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.util.ArrayMap;
+import android.util.Pair;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroupOverlay;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.widget.ImageView;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Base class for ExitTransitionCoordinator and EnterTransitionCoordinator, classes
+ * that manage activity transitions and the communications coordinating them between
+ * Activities. The ExitTransitionCoordinator is created in the
+ * ActivityOptions#makeSceneTransitionAnimation. The EnterTransitionCoordinator
+ * is created by ActivityOptions#createEnterActivityTransition by Activity when the window is
+ * attached.
+ *
+ * Typical startActivity goes like this:
+ * 1) ExitTransitionCoordinator created with ActivityOptions#makeSceneTransitionAnimation
+ * 2) Activity#startActivity called and that calls startExit() through
+ * ActivityOptions#dispatchStartExit
+ * - Exit transition starts by setting transitioning Views to INVISIBLE
+ * 3) Launched Activity starts, creating an EnterTransitionCoordinator.
+ * - The Window is made translucent
+ * - The Window background alpha is set to 0
+ * - The transitioning views are made INVISIBLE
+ * - MSG_SET_LISTENER is sent back to the ExitTransitionCoordinator.
+ * 4) The shared element transition completes.
+ * - MSG_TAKE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator
+ * 5) The MSG_TAKE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator.
+ * - Shared elements are made VISIBLE
+ * - Shared elements positions and size are set to match the end state of the calling
+ * Activity.
+ * - The shared element transition is started
+ * - If the window allows overlapping transitions, the views transition is started by setting
+ * the entering Views to VISIBLE and the background alpha is animated to opaque.
+ * - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator
+ * 6) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator
+ * - The shared elements are made INVISIBLE
+ * 7) The exit transition completes in the calling Activity.
+ * - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator.
+ * 8) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator.
+ * - If the window doesn't allow overlapping enter transitions, the enter transition is started
+ * by setting entering views to VISIBLE and the background is animated to opaque.
+ * 9) The background opacity animation completes.
+ * - The window is made opaque
+ * 10) The calling Activity gets an onStop() call
+ * - onActivityStopped() is called and all exited Views are made VISIBLE.
+ *
+ * Typical finishWithTransition goes like this:
+ * 1) finishWithTransition() calls startExit()
+ * - The Window start transitioning to Translucent
+ * - If no background exists, a black background is substituted
+ * - MSG_PREPARE_RESTORE is sent to the ExitTransitionCoordinator
+ * - The shared elements in the scene are matched against those shared elements
+ * that were sent by comparing the names.
+ * - The exit transition is started by setting Views to INVISIBLE.
+ * 2) MSG_PREPARE_RESTORE is received by the EnterTransitionCoordinator
+ * - All transitioning views are made VISIBLE to reverse what was done when onActivityStopped()
+ * was called
+ * 3) The Window is made translucent and a callback is received
+ * - The background alpha is animated to 0
+ * 4) The background alpha animation completes
+ * 5) The shared element transition completes
+ * - After both 4 & 5 complete, MSG_TAKE_SHARED_ELEMENTS is sent to the
+ * ExitTransitionCoordinator
+ * 6) MSG_TAKE_SHARED_ELEMENTS is received by ExitTransitionCoordinator
+ * - Shared elements are made VISIBLE
+ * - Shared elements positions and size are set to match the end state of the calling
+ * Activity.
+ * - The shared element transition is started
+ * - If the window allows overlapping transitions, the views transition is started by setting
+ * the entering Views to VISIBLE.
+ * - MSG_HIDE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator
+ * 7) MSG_HIDE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator
+ * - The shared elements are made INVISIBLE
+ * 8) The exit transition completes in the finishing Activity.
+ * - MSG_EXIT_TRANSITION_COMPLETE is sent to the ExitTransitionCoordinator.
+ * - finish() is called on the exiting Activity
+ * 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the ExitTransitionCoordinator.
+ * - If the window doesn't allow overlapping enter transitions, the enter transition is started
+ * by setting entering views to VISIBLE.
+ */
+abstract class ActivityTransitionCoordinator extends ResultReceiver {
+ private static final String TAG = "ActivityTransitionCoordinator";
+
+ /**
+ * 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.
+ */
+ public static final String KEY_SHARED_ELEMENT_NAMES = "android:shared_element_names";
+
+ public static final String KEY_SHARED_ELEMENT_STATE = "android:shared_element_state";
+
+ /**
+ * For Activity transitions, the called Activity's listener to receive calls
+ * when transitions complete.
+ */
+ static final String KEY_TRANSITION_RESULTS_RECEIVER = "android:transitionTargetListener";
+
+ private static final String KEY_SCREEN_X = "shared_element:screenX";
+ private static final String KEY_SCREEN_Y = "shared_element:screenY";
+ private static final String KEY_TRANSLATION_Z = "shared_element:translationZ";
+ private static final String KEY_WIDTH = "shared_element:width";
+ private static final String KEY_HEIGHT = "shared_element:height";
+ private static final String KEY_NAME = "shared_element:name";
+ private static final String KEY_BITMAP = "shared_element:bitmap";
+
+ /**
+ * Sent by the exiting coordinator (either EnterTransitionCoordinator
+ * or ExitTransitionCoordinator) after the shared elements have
+ * become stationary (shared element transition completes). This tells
+ * the remote coordinator to take control of the shared elements and
+ * that animations may begin. The remote Activity won't start entering
+ * until this message is received, but may wait for
+ * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true.
+ */
+ public static final int MSG_SET_LISTENER = 100;
+
+ /**
+ * Sent by the entering coordinator to tell the exiting coordinator
+ * to hide its shared elements after it has started its shared
+ * element transition. This is temporary until the
+ * interlock of shared elements is figured out.
+ */
+ public static final int MSG_HIDE_SHARED_ELEMENTS = 101;
+
+ /**
+ * Sent by the EnterTransitionCoordinator to tell the
+ * ExitTransitionCoordinator to hide all of its exited views after
+ * MSG_ACTIVITY_STOPPED has caused them all to show.
+ */
+ public static final int MSG_PREPARE_RESTORE = 102;
+
+ /**
+ * Sent by the exiting Activity in ActivityOptions#dispatchActivityStopped
+ * to leave the Activity in a good state after it has been hidden.
+ */
+ public static final int MSG_ACTIVITY_STOPPED = 103;
+
+ /**
+ * Sent by the exiting coordinator (either EnterTransitionCoordinator
+ * or ExitTransitionCoordinator) after the shared elements have
+ * become stationary (shared element transition completes). This tells
+ * the remote coordinator to take control of the shared elements and
+ * that animations may begin. The remote Activity won't start entering
+ * until this message is received, but may wait for
+ * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true.
+ */
+ public static final int MSG_TAKE_SHARED_ELEMENTS = 104;
+
+ /**
+ * Sent by the exiting coordinator (either
+ * EnterTransitionCoordinator or ExitTransitionCoordinator) after
+ * the exiting Views have finished leaving the scene. This will
+ * be ignored if allowOverlappingTransitions() is true on the
+ * remote coordinator. If it is false, it will trigger the enter
+ * transition to start.
+ */
+ public static final int MSG_EXIT_TRANSITION_COMPLETE = 105;
+
+ /**
+ * Sent by Activity#startActivity to begin the exit transition.
+ */
+ public static final int MSG_START_EXIT_TRANSITION = 106;
+
+ private Window mWindow;
+ private ArrayList<View> mSharedElements = new ArrayList<View>();
+ private ArrayList<String> mTargetSharedNames = new ArrayList<String>();
+ private ActivityOptions.ActivityTransitionListener mListener =
+ new ActivityOptions.ActivityTransitionListener();
+ private ArrayList<View> mEnteringViews;
+ private ResultReceiver mRemoteResultReceiver;
+ private boolean mNotifiedSharedElementTransitionComplete;
+ private boolean mNotifiedExitTransitionComplete;
+ private boolean mSharedElementTransitionStarted;
+
+ private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback();
+
+ private Transition.TransitionListener mSharedElementListener =
+ new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ transition.removeListener(this);
+ onSharedElementTransitionEnd();
+ }
+ };
+
+ private Transition.TransitionListener mExitListener =
+ new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ transition.removeListener(this);
+ onExitTransitionEnd();
+ }
+ };
+
+ public ActivityTransitionCoordinator(Window window)
+ {
+ super(new Handler());
+ mWindow = window;
+ }
+
+ // -------------------- ResultsReceiver Overrides ----------------------
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ switch (resultCode) {
+ case MSG_SET_LISTENER:
+ ResultReceiver resultReceiver
+ = resultData.getParcelable(KEY_TRANSITION_RESULTS_RECEIVER);
+ setRemoteResultReceiver(resultReceiver);
+ onSetResultReceiver();
+ break;
+ case MSG_HIDE_SHARED_ELEMENTS:
+ onHideSharedElements();
+ break;
+ case MSG_PREPARE_RESTORE:
+ onPrepareRestore();
+ break;
+ case MSG_EXIT_TRANSITION_COMPLETE:
+ if (!mSharedElementTransitionStarted) {
+ send(resultCode, resultData);
+ } else {
+ onRemoteSceneExitComplete();
+ }
+ break;
+ case MSG_TAKE_SHARED_ELEMENTS:
+ ArrayList<String> sharedElementNames
+ = resultData.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
+ Bundle sharedElementState = resultData.getBundle(KEY_SHARED_ELEMENT_STATE);
+ onTakeSharedElements(sharedElementNames, sharedElementState);
+ break;
+ case MSG_ACTIVITY_STOPPED:
+ onActivityStopped();
+ break;
+ case MSG_START_EXIT_TRANSITION:
+ startExit();
+ break;
+ }
+ }
+
+ // -------------------- calls that can be overridden by subclasses --------------------
+
+ /**
+ * Called when MSG_SET_LISTENER is received. This will only be received by
+ * ExitTransitionCoordinator.
+ */
+ protected void onSetResultReceiver() {}
+
+ /**
+ * Called when MSG_HIDE_SHARED_ELEMENTS is received
+ */
+ protected void onHideSharedElements() {
+ setViewVisibility(getSharedElements(), View.INVISIBLE);
+ mListener.onSharedElementTransferred(getSharedElementNames(), getSharedElements());
+ }
+
+ /**
+ * Called when MSG_PREPARE_RESTORE is called. This will only be received by
+ * ExitTransitionCoordinator.
+ */
+ protected void onPrepareRestore() {
+ mListener.onEnterReady();
+ }
+
+ /**
+ * Called when MSG_EXIT_TRANSITION_COMPLETE is received -- the remote coordinator has
+ * completed its exit transition. This can be called by the ExitTransitionCoordinator when
+ * starting an Activity or EnterTransitionCoordinator when called with finishWithTransition.
+ */
+ protected void onRemoteSceneExitComplete() {
+ if (!allowOverlappingTransitions()) {
+ Transition transition = beginTransition(mEnteringViews, false, true, true);
+ onStartEnterTransition(transition, mEnteringViews);
+ }
+ mListener.onRemoteExitComplete();
+ }
+
+ /**
+ * Called when MSG_TAKE_SHARED_ELEMENTS is received. This means that the shared elements are
+ * in a stable state and ready to move to the Window.
+ * @param sharedElementNames The names of the shared elements to move.
+ * @param state Contains the shared element states (size & position)
+ */
+ protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) {
+ setSharedElements();
+ reconcileSharedElements(sharedElementNames);
+ mEnteringViews.removeAll(mSharedElements);
+ final ArrayList<View> accepted = new ArrayList<View>();
+ final ArrayList<View> rejected = new ArrayList<View>();
+ createSharedElementImages(accepted, rejected, sharedElementNames, state);
+ setSharedElementState(state, accepted);
+ handleRejected(rejected);
+
+ if (getViewsTransition() != null) {
+ setViewVisibility(mEnteringViews, View.INVISIBLE);
+ }
+ setViewVisibility(mSharedElements, View.VISIBLE);
+ Transition transition = beginTransition(mEnteringViews, true, allowOverlappingTransitions(),
+ true);
+
+ if (allowOverlappingTransitions()) {
+ onStartEnterTransition(transition, mEnteringViews);
+ }
+
+ mRemoteResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null);
+ }
+
+ /**
+ * Called when MSG_ACTIVITY_STOPPED is received. This is received when Activity.onStop is
+ * called after running startActivity* is called using an Activity Transition.
+ */
+ protected void onActivityStopped() {}
+
+ /**
+ * Called when the start transition is ready to run. This may be immediately after
+ * MSG_TAKE_SHARED_ELEMENTS or MSG_EXIT_TRANSITION_COMPLETE, depending on whether
+ * overlapping transitions are allowed.
+ * @param transition The transition currently started.
+ * @param enteringViews The views entering the scene. This won't include shared elements.
+ */
+ protected void onStartEnterTransition(Transition transition, ArrayList<View> enteringViews) {
+ if (getViewsTransition() != null) {
+ setViewVisibility(enteringViews, View.VISIBLE);
+ }
+ mEnteringViews = null;
+ mListener.onStartEnterTransition(getSharedElementNames(), getSharedElements());
+ }
+
+ /**
+ * Called when the exit transition has started.
+ * @param exitingViews The views leaving the scene. This won't include shared elements.
+ */
+ protected void onStartExitTransition(ArrayList<View> exitingViews) {}
+
+ /**
+ * Called during the exit when the shared element transition has completed.
+ */
+ protected void onSharedElementTransitionEnd() {
+ Bundle bundle = new Bundle();
+ int[] tempLoc = new int[2];
+ for (int i = 0; i < mSharedElements.size(); i++) {
+ View sharedElement = mSharedElements.get(i);
+ String name = mTargetSharedNames.get(i);
+ captureSharedElementState(sharedElement, name, bundle, tempLoc);
+ }
+ Bundle allValues = new Bundle();
+ allValues.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, getSharedElementNames());
+ allValues.putBundle(KEY_SHARED_ELEMENT_STATE, bundle);
+ sharedElementTransitionComplete(allValues);
+ mListener.onSharedElementExitTransitionComplete();
+ }
+
+ /**
+ * Called after the shared element transition is complete to pass the shared element state
+ * to the remote coordinator.
+ * @param bundle The Bundle to send to the coordinator containing the shared element state.
+ */
+ protected abstract void sharedElementTransitionComplete(Bundle bundle);
+
+ /**
+ * Called when the exit transition finishes.
+ */
+ protected void onExitTransitionEnd() {
+ mListener.onExitTransitionComplete();
+ }
+
+ /**
+ * Called to start the exit transition. Launched from ActivityOptions#dispatchStartExit
+ */
+ protected abstract void startExit();
+
+ /**
+ * A non-null transition indicates that the Views of the Window should be made INVISIBLE.
+ * @return The Transition used to cause transitioning views to either enter or exit the scene.
+ */
+ protected abstract Transition getViewsTransition();
+
+ /**
+ * @return The Transition used to move the shared elements from the start position and size
+ * to the end position and size.
+ */
+ protected abstract Transition getSharedElementTransition();
+
+ /**
+ * @return When the enter transition should overlap with the exit transition of the
+ * remote controller.
+ */
+ protected abstract boolean allowOverlappingTransitions();
+
+ // called by subclasses
+
+ protected void notifySharedElementTransitionComplete(Bundle sharedElements) {
+ if (!mNotifiedSharedElementTransitionComplete) {
+ mNotifiedSharedElementTransitionComplete = true;
+ mRemoteResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, sharedElements);
+ }
+ }
+
+ protected void notifyExitTransitionComplete() {
+ if (!mNotifiedExitTransitionComplete) {
+ mNotifiedExitTransitionComplete = true;
+ mRemoteResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
+ }
+ }
+
+ protected void notifyPrepareRestore() {
+ mRemoteResultReceiver.send(MSG_PREPARE_RESTORE, null);
+ }
+
+ protected void setRemoteResultReceiver(ResultReceiver resultReceiver) {
+ mRemoteResultReceiver = resultReceiver;
+ }
+
+ protected void notifySetListener() {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(KEY_TRANSITION_RESULTS_RECEIVER, this);
+ mRemoteResultReceiver.send(MSG_SET_LISTENER, bundle);
+ }
+
+ protected void setEnteringViews(ArrayList<View> views) {
+ mEnteringViews = views;
+ }
+
+ protected void setSharedElements() {
+ Pair<View, String>[] sharedElements = mListener.getSharedElementsMapping();
+ mSharedElements.clear();
+ mTargetSharedNames.clear();
+ if (sharedElements == null) {
+ ArrayMap<String, View> map = new ArrayMap<String, View>();
+ if (getViewsTransition() != null) {
+ setViewVisibility(mEnteringViews, View.VISIBLE);
+ }
+ getDecor().findSharedElements(map);
+ if (getViewsTransition() != null) {
+ setViewVisibility(mEnteringViews, View.INVISIBLE);
+ }
+ for (int i = 0; i < map.size(); i++) {
+ View view = map.valueAt(i);
+ String name = map.keyAt(i);
+ mSharedElements.add(view);
+ mTargetSharedNames.add(name);
+ }
+ } else {
+ for (int i = 0; i < sharedElements.length; i++) {
+ Pair<View, String> viewStringPair = sharedElements[i];
+ View view = viewStringPair.first;
+ String name = viewStringPair.second;
+ mSharedElements.add(view);
+ mTargetSharedNames.add(name);
+ }
+ }
+ }
+
+ protected ArrayList<View> getSharedElements() {
+ return mSharedElements;
+ }
+
+ protected ArrayList<String> getSharedElementNames() {
+ return mTargetSharedNames;
+ }
+
+ protected Window getWindow() {
+ return mWindow;
+ }
+
+ protected ViewGroup getDecor() {
+ return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView();
+ }
+
+ protected void startExitTransition(ArrayList<String> sharedElements) {
+ setSharedElements();
+ reconcileSharedElements(sharedElements);
+ ArrayList<View> transitioningViews = captureTransitioningViews();
+ beginTransition(transitioningViews, true, true, false);
+ onStartExitTransition(transitioningViews);
+ if (getViewsTransition() != null) {
+ setViewVisibility(transitioningViews, View.INVISIBLE);
+ }
+ mListener.onStartExitTransition(getSharedElementNames(), getSharedElements());
+ }
+
+ protected void clearConnections() {
+ mRemoteResultReceiver = null;
+ }
+
+ // public API
+
+ public void setActivityTransitionListener(ActivityOptions.ActivityTransitionListener listener) {
+ if (listener == null) {
+ mListener = new ActivityOptions.ActivityTransitionListener();
+ } else {
+ mListener = listener;
+ }
+ }
+
+ // private methods
+
+ private Transition configureTransition(Transition transition) {
+ if (transition != null) {
+ transition = transition.clone();
+ transition.setEpicenterCallback(mEpicenterCallback);
+ }
+ return transition;
+ }
+
+ private void reconcileSharedElements(ArrayList<String> sharedElementNames) {
+ // keep only those that are in sharedElementNames.
+ int numSharedElements = sharedElementNames.size();
+ int targetIndex = 0;
+ for (int i = 0; i < numSharedElements; i++) {
+ String name = sharedElementNames.get(i);
+ int index = mTargetSharedNames.indexOf(name);
+ if (index >= 0) {
+ // Swap the items at the indexes if necessary.
+ if (index != targetIndex) {
+ View temp = mSharedElements.get(index);
+ mSharedElements.set(index, mSharedElements.get(targetIndex));
+ mSharedElements.set(targetIndex, temp);
+ mTargetSharedNames.set(index, mTargetSharedNames.get(targetIndex));
+ mTargetSharedNames.set(targetIndex, name);
+ }
+ targetIndex++;
+ }
+ }
+ for (int i = mSharedElements.size() - 1; i >= targetIndex; i--) {
+ mSharedElements.remove(i);
+ mTargetSharedNames.remove(i);
+ }
+ Rect epicenter = null;
+ if (!mTargetSharedNames.isEmpty()
+ && mTargetSharedNames.get(0).equals(sharedElementNames.get(0))) {
+ epicenter = calcEpicenter(mSharedElements.get(0));
+ }
+ mEpicenterCallback.setEpicenter(epicenter);
+ }
+
+ private void setSharedElementState(Bundle sharedElementState,
+ final ArrayList<View> acceptedOverlayViews) {
+ final int[] tempLoc = new int[2];
+ if (sharedElementState != null) {
+ for (int i = 0; i < mSharedElements.size(); i++) {
+ View sharedElement = mSharedElements.get(i);
+ View parent = (View) sharedElement.getParent();
+ parent.getLocationOnScreen(tempLoc);
+ String name = mTargetSharedNames.get(i);
+ setSharedElementState(sharedElement, name, sharedElementState, tempLoc);
+ sharedElement.requestLayout();
+ }
+ }
+ mListener.onCaptureSharedElementStart(mTargetSharedNames, mSharedElements,
+ acceptedOverlayViews);
+
+ getDecor().getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
+ mListener.onCaptureSharedElementEnd(mTargetSharedNames, mSharedElements,
+ acceptedOverlayViews);
+ mSharedElementTransitionStarted = true;
+ return true;
+ }
+ }
+ );
+ }
+
+ /**
+ * Sets the captured values from a previous
+ * {@link #captureSharedElementState(android.view.View, String, android.os.Bundle, int[])}
+ * @param view The View to apply placement changes to.
+ * @param name The shared element name given from the source Activity.
+ * @param transitionArgs A <code>Bundle</code> containing all placementinformation for named
+ * shared elements in the scene.
+ * @param parentLoc The x and y coordinates of the parent's screen position.
+ */
+ private static void setSharedElementState(View view, String name, Bundle transitionArgs,
+ int[] parentLoc) {
+ Bundle sharedElementBundle = transitionArgs.getBundle(name);
+ if (sharedElementBundle == null) {
+ return;
+ }
+
+ float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
+ view.setTranslationZ(z);
+
+ int x = sharedElementBundle.getInt(KEY_SCREEN_X);
+ int y = sharedElementBundle.getInt(KEY_SCREEN_Y);
+ int width = sharedElementBundle.getInt(KEY_WIDTH);
+ int height = sharedElementBundle.getInt(KEY_HEIGHT);
+
+ int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
+ int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
+ view.measure(widthSpec, heightSpec);
+
+ int left = x - parentLoc[0];
+ int top = y - parentLoc[1];
+ int right = left + width;
+ int bottom = top + height;
+ view.layout(left, top, right, bottom);
+ }
+
+ /**
+ * Captures placement information for Views with a shared element name for
+ * Activity Transitions.
+ * @param view The View to capture the placement information for.
+ * @param name The shared element name in the target Activity to apply the placement
+ * information for.
+ * @param transitionArgs Bundle to store shared element placement information.
+ * @param tempLoc A temporary int[2] for capturing the current location of views.
+ * @see #setSharedElementState(android.view.View, String, android.os.Bundle, int[])
+ */
+ private static void captureSharedElementState(View view, String name, Bundle transitionArgs,
+ int[] tempLoc) {
+ Bundle sharedElementBundle = new Bundle();
+ view.getLocationOnScreen(tempLoc);
+ float scaleX = view.getScaleX();
+ sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]);
+ int width = Math.round(view.getWidth() * scaleX);
+ sharedElementBundle.putInt(KEY_WIDTH, width);
+
+ float scaleY = view.getScaleY();
+ sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]);
+ int height= Math.round(view.getHeight() * scaleY);
+ sharedElementBundle.putInt(KEY_HEIGHT, height);
+
+ sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
+
+ sharedElementBundle.putString(KEY_NAME, view.getSharedElementName());
+
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ view.draw(canvas);
+ sharedElementBundle.putParcelable(KEY_BITMAP, bitmap);
+
+ transitionArgs.putBundle(name, sharedElementBundle);
+ }
+
+ private static Rect calcEpicenter(View view) {
+ int[] loc = new int[2];
+ view.getLocationOnScreen(loc);
+ int left = loc[0] + Math.round(view.getTranslationX());
+ int top = loc[1] + Math.round(view.getTranslationY());
+ int right = left + view.getWidth();
+ int bottom = top + view.getHeight();
+ return new Rect(left, top, right, bottom);
+ }
+
+ public static void setViewVisibility(Collection<View> views, int visibility) {
+ if (views != null) {
+ for (View view : views) {
+ view.setVisibility(visibility);
+ }
+ }
+ }
+
+ private static Transition addTransitionTargets(Transition transition, Collection<View> views) {
+ if (transition == null || views == null || views.isEmpty()) {
+ return null;
+ }
+ TransitionSet set = new TransitionSet();
+ set.addTransition(transition.clone());
+ if (views != null) {
+ for (View view: views) {
+ set.addTarget(view);
+ }
+ }
+ return set;
+ }
+
+ private ArrayList<View> captureTransitioningViews() {
+ if (getViewsTransition() == null) {
+ return null;
+ }
+ ArrayList<View> transitioningViews = new ArrayList<View>();
+ getDecor().captureTransitioningViews(transitioningViews);
+ transitioningViews.removeAll(getSharedElements());
+ return transitioningViews;
+ }
+
+ private Transition getSharedElementTransition(boolean isEnter) {
+ Transition transition = getSharedElementTransition();
+ if (transition == null) {
+ return null;
+ }
+ transition = configureTransition(transition);
+ if (!isEnter) {
+ transition.addListener(mSharedElementListener);
+ }
+ return transition;
+ }
+
+ private Transition getViewsTransition(ArrayList<View> transitioningViews, boolean isEnter) {
+ Transition transition = getViewsTransition();
+ if (transition == null) {
+ return null;
+ }
+ transition = configureTransition(transition);
+ if (!isEnter) {
+ transition.addListener(mExitListener);
+ }
+ return addTransitionTargets(transition, transitioningViews);
+ }
+
+ private Transition beginTransition(ArrayList<View> transitioningViews,
+ boolean transitionSharedElement, boolean transitionViews, boolean isEnter) {
+ Transition sharedElementTransition = null;
+ if (transitionSharedElement) {
+ sharedElementTransition = getSharedElementTransition(isEnter);
+ if (!isEnter && sharedElementTransition == null) {
+ onSharedElementTransitionEnd();
+ }
+ }
+ Transition viewsTransition = null;
+ if (transitionViews) {
+ viewsTransition = getViewsTransition(transitioningViews, isEnter);
+ if (!isEnter && viewsTransition == null) {
+ onExitTransitionEnd();
+ }
+ }
+
+ Transition transition = null;
+ if (sharedElementTransition == null) {
+ transition = viewsTransition;
+ } else if (viewsTransition == null) {
+ transition = sharedElementTransition;
+ } else {
+ TransitionSet set = new TransitionSet();
+ set.addTransition(sharedElementTransition);
+ set.addTransition(viewsTransition);
+ transition = set;
+ }
+ if (transition != null) {
+ TransitionManager.beginDelayedTransition(getDecor(), transition);
+ if (transitionSharedElement && !mSharedElements.isEmpty()) {
+ mSharedElements.get(0).invalidate();
+ } else if (transitionViews && !transitioningViews.isEmpty()) {
+ transitioningViews.get(0).invalidate();
+ }
+ }
+ return transition;
+ }
+
+ private void handleRejected(final ArrayList<View> rejected) {
+ int numRejected = rejected.size();
+ if (numRejected == 0) {
+ return;
+ }
+ boolean rejectionHandled = mListener.handleRejectedSharedElements(rejected);
+ if (rejectionHandled) {
+ return;
+ }
+
+ ViewGroupOverlay overlay = getDecor().getOverlay();
+ ObjectAnimator animator = null;
+ for (int i = 0; i < numRejected; i++) {
+ View view = rejected.get(i);
+ overlay.add(view);
+ animator = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0);
+ animator.start();
+ }
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ViewGroupOverlay overlay = getDecor().getOverlay();
+ for (int i = rejected.size() - 1; i >= 0; i--) {
+ overlay.remove(rejected.get(i));
+ }
+ }
+ });
+ }
+
+ private void createSharedElementImages(ArrayList<View> accepted, ArrayList<View> rejected,
+ ArrayList<String> sharedElementNames, Bundle state) {
+ int numSharedElements = sharedElementNames.size();
+ Context context = getWindow().getContext();
+ int[] parentLoc = new int[2];
+ getDecor().getLocationOnScreen(parentLoc);
+ for (int i = 0; i < numSharedElements; i++) {
+ String name = sharedElementNames.get(i);
+ Bundle sharedElementBundle = state.getBundle(name);
+ if (sharedElementBundle != null) {
+ Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP);
+ ImageView imageView = new ImageView(context);
+ imageView.setId(com.android.internal.R.id.shared_element);
+ imageView.setScaleType(ImageView.ScaleType.CENTER);
+ imageView.setImageBitmap(bitmap);
+ imageView.setSharedElementName(name);
+ setSharedElementState(imageView, name, state, parentLoc);
+ if (mTargetSharedNames.contains(name)) {
+ accepted.add(imageView);
+ } else {
+ rejected.add(imageView);
+ }
+ }
+ }
+ }
+
+ private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
+ private Rect mEpicenter;
+
+ public void setEpicenter(Rect epicenter) { mEpicenter = epicenter; }
+
+ @Override
+ public Rect getEpicenter(Transition transition) {
+ return mEpicenter;
+ }
+ }
+}
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..fcc7f8e 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -33,6 +33,7 @@ import android.os.RemoteException;
import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import com.android.internal.app.IVoiceInteractor;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -113,7 +114,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;
}
@@ -135,6 +137,8 @@ public abstract class ApplicationThreadNative extends Binder
ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
Configuration curConfig = Configuration.CREATOR.createFromParcel(data);
CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
+ IVoiceInteractor voiceInteractor = IVoiceInteractor.Stub.asInterface(
+ data.readStrongBinder());
int procState = data.readInt();
Bundle state = data.readBundle();
List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
@@ -145,8 +149,11 @@ public abstract class ApplicationThreadNative extends Binder
ParcelFileDescriptor profileFd = data.readInt() != 0
? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null;
boolean autoStopProfiler = data.readInt() != 0;
- scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, procState, state,
- ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler);
+ Bundle resumeArgs = data.readBundle();
+ scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo,
+ voiceInteractor, procState, state,
+ ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler,
+ resumeArgs);
return true;
}
@@ -705,20 +712,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);
@@ -730,10 +739,12 @@ class ApplicationThreadProxy implements IApplicationThread {
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
+ IVoiceInteractor voiceInteractor,
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);
@@ -742,6 +753,7 @@ class ApplicationThreadProxy implements IApplicationThread {
info.writeToParcel(data, 0);
curConfig.writeToParcel(data, 0);
compatInfo.writeToParcel(data, 0);
+ data.writeStrongBinder(voiceInteractor != null ? voiceInteractor.asBinder() : null);
data.writeInt(procState);
data.writeBundle(state);
data.writeTypedList(pendingResults);
@@ -756,6 +768,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 +899,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..a4b2651 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.SessionManager;
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);
@@ -457,13 +473,14 @@ class ContextImpl extends Context {
registerService(NOTIFICATION_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
final Context outerContext = ctx.getOuterContext();
+ // TODO: Why are we not just using the theme attribute
+ // that defines the dialog theme?
return new NotificationManager(
new ContextThemeWrapper(outerContext,
- Resources.selectSystemTheme(0,
+ outerContext.getResources().selectSystemTheme(0,
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.array.system_theme_sdks,
+ com.android.internal.R.array.system_theme_dialog_styles)),
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 SessionManager(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) {
@@ -674,7 +731,7 @@ class ContextImpl extends Context {
@Override
public Resources.Theme getTheme() {
if (mTheme == null) {
- mThemeResource = Resources.selectDefaultTheme(mThemeResource,
+ mThemeResource = mResources.selectDefaultTheme(mThemeResource,
getOuterContext().getApplicationInfo().targetSdkVersion);
mTheme = mResources.newTheme();
mTheme.applyStyle(mThemeResource, true);
@@ -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;
}
}
}
@@ -1808,17 +1865,26 @@ class ContextImpl extends Context {
}
private String uriModeFlagToString(int uriModeFlags) {
- switch (uriModeFlags) {
- case Intent.FLAG_GRANT_READ_URI_PERMISSION |
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION:
- return "read and write";
- case Intent.FLAG_GRANT_READ_URI_PERMISSION:
- return "read";
- case Intent.FLAG_GRANT_WRITE_URI_PERMISSION:
- return "write";
+ StringBuilder builder = new StringBuilder();
+ if ((uriModeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+ builder.append("read and ");
+ }
+ if ((uriModeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+ builder.append("write and ");
+ }
+ if ((uriModeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0) {
+ builder.append("persistable and ");
+ }
+ if ((uriModeFlags & Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) != 0) {
+ builder.append("prefix and ");
+ }
+
+ if (builder.length() > 5) {
+ builder.setLength(builder.length() - 5);
+ return builder.toString();
+ } else {
+ throw new IllegalArgumentException("Unknown permission mode flags: " + uriModeFlags);
}
- throw new IllegalArgumentException(
- "Unknown permission mode flags: " + uriModeFlags);
}
private void enforceForUri(
@@ -2036,8 +2102,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/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
new file mode 100644
index 0000000..cbb8359
--- /dev/null
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -0,0 +1,287 @@
+/*
+ * 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.transition.Transition;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+
+import java.util.ArrayList;
+
+/**
+ * This ActivityTransitionCoordinator is created by the Activity to manage
+ * the enter scene and shared element transfer as well as Activity#finishWithTransition
+ * exiting the Scene and transferring shared elements back to the called Activity.
+ */
+class EnterTransitionCoordinator extends ActivityTransitionCoordinator
+ implements ViewTreeObserver.OnPreDrawListener {
+ private static final String TAG = "EnterTransitionCoordinator";
+
+ // The background fade in/out duration. 150ms is pretty quick, but not abrupt.
+ private static final int FADE_BACKGROUND_DURATION_MS = 150;
+
+ /**
+ * The shared element names sent by the ExitTransitionCoordinator and may be
+ * shared when exiting back.
+ */
+ private ArrayList<String> mEnteringSharedElementNames;
+
+ /**
+ * The Activity that has created this coordinator. This is used solely to make the
+ * Window translucent/opaque.
+ */
+ private Activity mActivity;
+
+ /**
+ * True if the Window was opaque at the start and we should make it opaque again after
+ * enter transitions have completed.
+ */
+ private boolean mWasOpaque;
+
+ /**
+ * During exit, is the background alpha == 0?
+ */
+ private boolean mBackgroundFadedOut;
+
+ /**
+ * During exit, has the shared element transition completed?
+ */
+ private boolean mSharedElementTransitionComplete;
+
+ /**
+ * Has the exit started? We don't want to accidentally exit multiple times. e.g. when
+ * back is hit twice during the exit animation.
+ */
+ private boolean mExitTransitionStarted;
+
+ /**
+ * Has the exit transition ended?
+ */
+ private boolean mExitTransitionComplete;
+
+ /**
+ * We only want to make the Window transparent and set the background alpha once. After that,
+ * the Activity won't want the same enter transition.
+ */
+ private boolean mMadeReady;
+
+ /**
+ * True if Window.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS) -- this means that
+ * enter and exit transitions should be active.
+ */
+ private boolean mSupportsTransition;
+
+ /**
+ * Background alpha animations may complete prior to receiving the callback for
+ * onTranslucentConversionComplete. If so, we need to immediately call to make the Window
+ * opaque.
+ */
+ private boolean mMakeOpaque;
+
+ public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver) {
+ super(activity.getWindow());
+ mActivity = activity;
+ setRemoteResultReceiver(resultReceiver);
+ }
+
+ public void readyToEnter() {
+ if (!mMadeReady) {
+ mMadeReady = true;
+ mSupportsTransition = getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS);
+ if (mSupportsTransition) {
+ Window window = getWindow();
+ window.getDecorView().getViewTreeObserver().addOnPreDrawListener(this);
+ mActivity.overridePendingTransition(0, 0);
+ mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
+ @Override
+ public void onTranslucentConversionComplete(boolean drawComplete) {
+ mWasOpaque = true;
+ if (mMakeOpaque) {
+ mActivity.convertFromTranslucent();
+ }
+ }
+ });
+ Drawable background = getDecor().getBackground();
+ if (background != null) {
+ window.setBackgroundDrawable(null);
+ background.setAlpha(0);
+ window.setBackgroundDrawable(background);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) {
+ mEnteringSharedElementNames = new ArrayList<String>();
+ mEnteringSharedElementNames.addAll(sharedElementNames);
+ super.onTakeSharedElements(sharedElementNames, state);
+ }
+
+ @Override
+ protected void sharedElementTransitionComplete(Bundle bundle) {
+ notifySharedElementTransitionComplete(bundle);
+ exitAfterSharedElementTransition();
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this);
+ setEnteringViews(readyEnteringViews());
+ notifySetListener();
+ onPrepareRestore();
+ return false;
+ }
+
+ @Override
+ public void startExit() {
+ if (!mExitTransitionStarted) {
+ mExitTransitionStarted = true;
+ startExitTransition(mEnteringSharedElementNames);
+ }
+ }
+
+ @Override
+ protected Transition getViewsTransition() {
+ if (!mSupportsTransition) {
+ return null;
+ }
+ return getWindow().getEnterTransition();
+ }
+
+ @Override
+ protected Transition getSharedElementTransition() {
+ if (!mSupportsTransition) {
+ return null;
+ }
+ return getWindow().getSharedElementEnterTransition();
+ }
+
+ @Override
+ protected void onStartEnterTransition(Transition transition, ArrayList<View> enteringViews) {
+ Drawable background = getDecor().getBackground();
+ if (background != null) {
+ ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 255);
+ animator.setDuration(FADE_BACKGROUND_DURATION_MS);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mMakeOpaque = true;
+ if (mWasOpaque) {
+ mActivity.convertFromTranslucent();
+ }
+ }
+ });
+ animator.start();
+ } else if (mWasOpaque) {
+ transition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ mMakeOpaque = true;
+ mActivity.convertFromTranslucent();
+ }
+ });
+ }
+ super.onStartEnterTransition(transition, enteringViews);
+ }
+
+ public ArrayList<View> readyEnteringViews() {
+ ArrayList<View> enteringViews = new ArrayList<View>();
+ getDecor().captureTransitioningViews(enteringViews);
+ if (getViewsTransition() != null) {
+ setViewVisibility(enteringViews, View.INVISIBLE);
+ }
+ return enteringViews;
+ }
+
+ @Override
+ protected void startExitTransition(ArrayList<String> sharedElements) {
+ mMakeOpaque = false;
+ notifyPrepareRestore();
+
+ if (getDecor().getBackground() == null) {
+ ColorDrawable black = new ColorDrawable(0xFF000000);
+ getWindow().setBackgroundDrawable(black);
+ }
+ if (mWasOpaque) {
+ mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
+ @Override
+ public void onTranslucentConversionComplete(boolean drawComplete) {
+ fadeOutBackground();
+ }
+ });
+ } else {
+ fadeOutBackground();
+ }
+
+ super.startExitTransition(sharedElements);
+ }
+
+ private void fadeOutBackground() {
+ ObjectAnimator animator = ObjectAnimator.ofInt(getDecor().getBackground(),
+ "alpha", 0);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBackgroundFadedOut = true;
+ if (mSharedElementTransitionComplete) {
+ EnterTransitionCoordinator.super.onSharedElementTransitionEnd();
+ }
+ }
+ });
+ animator.setDuration(FADE_BACKGROUND_DURATION_MS);
+ animator.start();
+ }
+
+ @Override
+ protected void onExitTransitionEnd() {
+ mExitTransitionComplete = true;
+ exitAfterSharedElementTransition();
+ super.onExitTransitionEnd();
+ }
+
+ @Override
+ protected void onSharedElementTransitionEnd() {
+ mSharedElementTransitionComplete = true;
+ if (mBackgroundFadedOut) {
+ super.onSharedElementTransitionEnd();
+ }
+ }
+
+ @Override
+ protected boolean allowOverlappingTransitions() {
+ return getWindow().getAllowEnterTransitionOverlap();
+ }
+
+ private void exitAfterSharedElementTransition() {
+ if (mSharedElementTransitionComplete && mExitTransitionComplete && mBackgroundFadedOut) {
+ mActivity.finish();
+ if (mSupportsTransition) {
+ mActivity.overridePendingTransition(0, 0);
+ }
+ notifyExitTransitionComplete();
+ clearConnections();
+ }
+ }
+}
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
new file mode 100644
index 0000000..d920787
--- /dev/null
+++ b/core/java/android/app/ExitTransitionCoordinator.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.app;
+
+import android.os.Bundle;
+import android.transition.Transition;
+import android.util.Pair;
+import android.view.View;
+import android.view.Window;
+
+import java.util.ArrayList;
+
+/**
+ * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation
+ * to govern the exit of the Scene and the shared elements when calling an Activity as well as
+ * the reentry of the Scene when coming back from the called Activity.
+ */
+class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
+ private static final String TAG = "ExitTransitionCoordinator";
+
+ /**
+ * The Views that have exited and need to be restored to VISIBLE when returning to the
+ * normal state.
+ */
+ private ArrayList<View> mTransitioningViews;
+
+ /**
+ * Has the exit started? We don't want to accidentally exit multiple times.
+ */
+ private boolean mExitStarted;
+
+ /**
+ * Has the called Activity's ResultReceiver been set?
+ */
+ private boolean mIsResultReceiverSet;
+
+ /**
+ * Has the exit transition completed? If so, we can notify as soon as the ResultReceiver
+ * has been set.
+ */
+ private boolean mExitComplete;
+
+ /**
+ * Has the shared element transition completed? If so, we can notify as soon as the
+ * ResultReceiver has been set.
+ */
+ private Bundle mSharedElements;
+
+ /**
+ * Has the shared element transition completed?
+ */
+ private boolean mSharedElementsComplete;
+
+ public ExitTransitionCoordinator(Window window,
+ ActivityOptions.ActivityTransitionListener listener) {
+ super(window);
+ setActivityTransitionListener(listener);
+ }
+
+ @Override
+ protected void onSetResultReceiver() {
+ mIsResultReceiverSet = true;
+ notifyCompletions();
+ }
+
+ @Override
+ protected void onPrepareRestore() {
+ makeTransitioningViewsInvisible();
+ setEnteringViews(mTransitioningViews);
+ mTransitioningViews = null;
+ super.onPrepareRestore();
+ }
+
+ @Override
+ protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) {
+ super.onTakeSharedElements(sharedElementNames, state);
+ clearConnections();
+ }
+
+ @Override
+ protected void onActivityStopped() {
+ if (getViewsTransition() != null) {
+ setViewVisibility(mTransitioningViews, View.VISIBLE);
+ }
+ super.onActivityStopped();
+ }
+
+ @Override
+ protected void sharedElementTransitionComplete(Bundle bundle) {
+ mSharedElements = bundle;
+ mSharedElementsComplete = true;
+ notifyCompletions();
+ }
+
+ @Override
+ protected void onExitTransitionEnd() {
+ mExitComplete = true;
+ notifyCompletions();
+ super.onExitTransitionEnd();
+ }
+
+ private void notifyCompletions() {
+ if (mIsResultReceiverSet && mSharedElementsComplete) {
+ if (mSharedElements != null) {
+ notifySharedElementTransitionComplete(mSharedElements);
+ mSharedElements = null;
+ }
+ if (mExitComplete) {
+ notifyExitTransitionComplete();
+ }
+ }
+ }
+
+ @Override
+ public void startExit() {
+ if (!mExitStarted) {
+ mExitStarted = true;
+ setSharedElements();
+ startExitTransition(getSharedElementNames());
+ }
+ }
+
+ @Override
+ protected Transition getViewsTransition() {
+ if (!getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
+ return null;
+ }
+ return getWindow().getExitTransition();
+ }
+
+ @Override
+ protected Transition getSharedElementTransition() {
+ if (!getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
+ return null;
+ }
+ return getWindow().getSharedElementExitTransition();
+ }
+
+ private void makeTransitioningViewsInvisible() {
+ if (getViewsTransition() != null) {
+ setViewVisibility(mTransitioningViews, View.INVISIBLE);
+ }
+ }
+
+ @Override
+ protected void onStartExitTransition(ArrayList<View> exitingViews) {
+ mTransitioningViews = new ArrayList<View>();
+ if (exitingViews != null) {
+ mTransitioningViews.addAll(exitingViews);
+ }
+ mTransitioningViews.addAll(getSharedElements());
+ }
+
+ @Override
+ protected boolean allowOverlappingTransitions() {
+ return getWindow().getAllowExitTransitionOverlap();
+ }
+}
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..6b94c4e 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -47,6 +47,8 @@ import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.StrictMode;
+import android.service.voice.IVoiceInteractionSession;
+import com.android.internal.app.IVoiceInteractor;
import java.util.List;
@@ -77,9 +79,13 @@ public interface IActivityManager extends IInterface {
IntentSender intent, Intent fillInIntent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode,
int flagsMask, int flagsValues, Bundle options) throws RemoteException;
+ public int startVoiceActivity(String callingPackage, int callingPid, int callingUid,
+ Intent intent, String resolvedType, IVoiceInteractionSession session,
+ IVoiceInteractor interactor, int flags, String profileFile,
+ ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException;
public boolean startNextMatchingActivity(IBinder callingActivity,
Intent intent, Bundle options) throws RemoteException;
- public boolean finishActivity(IBinder token, int code, Intent data)
+ public boolean finishActivity(IBinder token, int code, Intent data, boolean finishTask)
throws RemoteException;
public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException;
public boolean finishActivityAffinity(IBinder token) throws RemoteException;
@@ -122,6 +128,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 +248,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 +352,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 +371,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 +426,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 +728,16 @@ 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;
+ int START_VOICE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+218;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 3aceff9..f290e94 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -31,6 +31,8 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.IInterface;
+import android.service.voice.IVoiceInteractionSession;
+import com.android.internal.app.IVoiceInteractor;
import java.io.FileDescriptor;
import java.util.List;
@@ -50,15 +52,17 @@ 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;
+ IVoiceInteractor voiceInteractor, int procState, Bundle state,
+ List<ResultInfo> pendingResults, 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..b917263 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -18,11 +18,16 @@
package android.app;
import android.app.ITransientNotification;
-import android.service.notification.StatusBarNotification;
import android.app.Notification;
import android.content.ComponentName;
import android.content.Intent;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.IConditionListener;
+import android.service.notification.IConditionProvider;
import android.service.notification.INotificationListener;
+import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenModeConfig;
/** {@hide} */
interface INotificationManager
@@ -31,7 +36,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);
@@ -49,4 +54,12 @@ interface INotificationManager
StatusBarNotification[] getActiveNotificationsFromListener(in INotificationListener token, in String[] keys);
String[] getActiveNotificationKeysFromListener(in INotificationListener token);
+
+ ZenModeConfig getZenModeConfig();
+ boolean setZenModeConfig(in ZenModeConfig config);
+ oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
+ oneway void requestZenModeConditions(in IConditionListener callback, int relevance);
+ oneway void setZenModeCondition(in Uri conditionId);
+ oneway void setAutomaticZenModeConditions(in Uri[] conditionIds);
+ Condition[] getAutomaticZenModeConditions();
} \ No newline at end of file
diff --git a/core/java/android/app/IProcessObserver.aidl b/core/java/android/app/IProcessObserver.aidl
index e587912..ecf2c73 100644
--- a/core/java/android/app/IProcessObserver.aidl
+++ b/core/java/android/app/IProcessObserver.aidl
@@ -20,7 +20,7 @@ package android.app;
oneway interface IProcessObserver {
void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities);
- void onImportanceChanged(int pid, int uid, int importance);
+ void onProcessStateChanged(int pid, int uid, int procState);
void onProcessDied(int pid, int uid);
}
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/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 028fa68..e58ccb8 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1428,7 +1428,7 @@ public class Instrumentation {
}
/**
- * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
+ * Like {@link #execStartActivity},
* but accepts an array of activities to be started. Note that active
* {@link ActivityMonitor} objects only match against the first activity in
* the array.
@@ -1442,7 +1442,7 @@ public class Instrumentation {
}
/**
- * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
+ * Like {@link #execStartActivity},
* but accepts an array of activities to be started. Note that active
* {@link ActivityMonitor} objects only match against the first activity in
* the array.
@@ -1545,8 +1545,7 @@ public class Instrumentation {
}
/**
- * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
- * but for starting as a particular user.
+ * Like {@link #execStartActivity}, but for starting as a particular user.
*
* @param who The Context from which the activity is being started.
* @param contextThread The main thread of the Context from which the activity
@@ -1616,7 +1615,8 @@ public class Instrumentation {
mUiAutomationConnection = uiAutomationConnection;
}
- /*package*/ static void checkStartActivityResult(int res, Object intent) {
+ /** @hide */
+ public static void checkStartActivityResult(int res, Object intent) {
if (res >= ActivityManager.START_SUCCESS) {
return;
}
@@ -1640,6 +1640,9 @@ public class Instrumentation {
case ActivityManager.START_NOT_ACTIVITY:
throw new IllegalArgumentException(
"PendingIntent is not an activity");
+ case ActivityManager.START_NOT_VOICE_COMPATIBLE:
+ throw new SecurityException(
+ "Starting under voice control not allowed for: " + intent);
default:
throw new AndroidRuntimeException("Unknown error code "
+ res + " when starting " + intent);
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 55e7470..25a1493 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 a8896c2..4427ce1 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -45,7 +45,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.IoUtils;
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 2045ed8..ce5306f 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -38,6 +38,7 @@ 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_SYSTEM_INFO = View.STATUS_BAR_DISABLE_SYSTEM_INFO;
@@ -59,6 +60,7 @@ public class StatusBarManager {
| DISABLE_SEARCH;
public static final int NAVIGATION_HINT_BACK_ALT = 1 << 0;
+ public static final int NAVIGATION_HINT_IME_SHOWN = 1 << 1;
public static final int WINDOW_STATUS_BAR = 1;
public static final int WINDOW_NAVIGATION_BAR = 2;
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/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
new file mode 100644
index 0000000..6dc48b0
--- /dev/null
+++ b/core/java/android/app/VoiceInteractor.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 android.app;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.app.IVoiceInteractorCallback;
+import com.android.internal.app.IVoiceInteractorRequest;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+import java.util.WeakHashMap;
+
+/**
+ * Interface for an {@link Activity} to interact with the user through voice.
+ */
+public class VoiceInteractor {
+ static final String TAG = "VoiceInteractor";
+ static final boolean DEBUG = true;
+
+ final Context mContext;
+ final Activity mActivity;
+ final IVoiceInteractor mInteractor;
+ final HandlerCaller mHandlerCaller;
+ final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() {
+ @Override
+ public void executeMessage(Message msg) {
+ SomeArgs args = (SomeArgs)msg.obj;
+ Request request;
+ switch (msg.what) {
+ case MSG_CONFIRMATION_RESULT:
+ request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
+ if (DEBUG) Log.d(TAG, "onConfirmResult: req="
+ + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
+ + " confirmed=" + msg.arg1 + " result=" + args.arg2);
+ if (request != null) {
+ ((ConfirmationRequest)request).onConfirmationResult(msg.arg1 != 0,
+ (Bundle) args.arg2);
+ request.clear();
+ }
+ break;
+ case MSG_COMMAND_RESULT:
+ request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0);
+ if (DEBUG) Log.d(TAG, "onCommandResult: req="
+ + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
+ + " result=" + args.arg2);
+ if (request != null) {
+ ((CommandRequest)request).onCommandResult((Bundle) args.arg2);
+ if (msg.arg1 != 0) {
+ request.clear();
+ }
+ }
+ break;
+ case MSG_CANCEL_RESULT:
+ request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
+ if (DEBUG) Log.d(TAG, "onCancelResult: req="
+ + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request);
+ if (request != null) {
+ request.onCancel();
+ request.clear();
+ }
+ break;
+ }
+ }
+ };
+
+ final IVoiceInteractorCallback.Stub mCallback = new IVoiceInteractorCallback.Stub() {
+ @Override
+ public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
+ Bundle result) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
+ MSG_CONFIRMATION_RESULT, confirmed ? 1 : 0, request, result));
+ }
+
+ @Override
+ public void deliverCommandResult(IVoiceInteractorRequest request, boolean complete,
+ Bundle result) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
+ MSG_COMMAND_RESULT, complete ? 1 : 0, request, result));
+ }
+
+ @Override
+ public void deliverCancel(IVoiceInteractorRequest request) throws RemoteException {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(
+ MSG_CANCEL_RESULT, request));
+ }
+ };
+
+ final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
+
+ static final int MSG_CONFIRMATION_RESULT = 1;
+ static final int MSG_COMMAND_RESULT = 2;
+ static final int MSG_CANCEL_RESULT = 3;
+
+ public static abstract class Request {
+ IVoiceInteractorRequest mRequestInterface;
+ Context mContext;
+ Activity mActivity;
+
+ public Request() {
+ }
+
+ public void cancel() {
+ try {
+ mRequestInterface.cancel();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Voice interactor has died", e);
+ }
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public Activity getActivity() {
+ return mActivity;
+ }
+
+ public void onCancel() {
+ }
+
+ void clear() {
+ mRequestInterface = null;
+ mContext = null;
+ mActivity = null;
+ }
+
+ abstract IVoiceInteractorRequest submit(IVoiceInteractor interactor,
+ String packageName, IVoiceInteractorCallback callback) throws RemoteException;
+ }
+
+ public static class ConfirmationRequest extends Request {
+ final CharSequence mPrompt;
+ final Bundle mExtras;
+
+ /**
+ * Confirms an operation with the user via the trusted system
+ * VoiceInteractionService. This allows an Activity to complete an unsafe operation that
+ * would require the user to touch the screen when voice interaction mode is not enabled.
+ * The result of the confirmation will be returned through an asynchronous call to
+ * either {@link #onConfirmationResult(boolean, android.os.Bundle)} or
+ * {@link #onCancel()}.
+ *
+ * <p>In some cases this may be a simple yes / no confirmation or the confirmation could
+ * include context information about how the action will be completed
+ * (e.g. booking a cab might include details about how long until the cab arrives)
+ * so the user can give a confirmation.
+ * @param prompt Optional confirmation text to read to the user as the action being
+ * confirmed.
+ * @param extras Additional optional information.
+ */
+ public ConfirmationRequest(CharSequence prompt, Bundle extras) {
+ mPrompt = prompt;
+ mExtras = extras;
+ }
+
+ public void onConfirmationResult(boolean confirmed, Bundle result) {
+ }
+
+ IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
+ IVoiceInteractorCallback callback) throws RemoteException {
+ return interactor.startConfirmation(packageName, callback, mPrompt.toString(), mExtras);
+ }
+ }
+
+ public static class CommandRequest extends Request {
+ final String mCommand;
+ final Bundle mArgs;
+
+ /**
+ * Execute a command using the trusted system VoiceInteractionService.
+ * This allows an Activity to request additional information from the user needed to
+ * complete an action (e.g. booking a table might have several possible times that the
+ * user could select from or an app might need the user to agree to a terms of service).
+ * The result of the confirmation will be returned through an asynchronous call to
+ * either {@link #onCommandResult(android.os.Bundle)} or
+ * {@link #onCancel()}.
+ *
+ * <p>The command is a string that describes the generic operation to be performed.
+ * The command will determine how the properties in extras are interpreted and the set of
+ * available commands is expected to grow over time. An example might be
+ * "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number of bags as part of
+ * airline check-in. (This is not an actual working example.)
+ *
+ * @param command The desired command to perform.
+ * @param args Additional arguments to control execution of the command.
+ */
+ public CommandRequest(String command, Bundle args) {
+ mCommand = command;
+ mArgs = args;
+ }
+
+ public void onCommandResult(Bundle result) {
+ }
+
+ IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
+ IVoiceInteractorCallback callback) throws RemoteException {
+ return interactor.startConfirmation(packageName, callback, mCommand, mArgs);
+ }
+ }
+
+ VoiceInteractor(Context context, Activity activity, IVoiceInteractor interactor,
+ Looper looper) {
+ mContext = context;
+ mActivity = activity;
+ mInteractor = interactor;
+ mHandlerCaller = new HandlerCaller(context, looper, mHandlerCallerCallback, true);
+ }
+
+ Request pullRequest(IVoiceInteractorRequest request, boolean complete) {
+ synchronized (mActiveRequests) {
+ Request req = mActiveRequests.get(request.asBinder());
+ if (req != null && complete) {
+ mActiveRequests.remove(request.asBinder());
+ }
+ return req;
+ }
+ }
+
+ public boolean submitRequest(Request request) {
+ try {
+ IVoiceInteractorRequest ireq = request.submit(mInteractor,
+ mContext.getOpPackageName(), mCallback);
+ request.mRequestInterface = ireq;
+ request.mContext = mContext;
+ request.mActivity = mActivity;
+ synchronized (mActiveRequests) {
+ mActiveRequests.put(ireq.asBinder(), request);
+ }
+ return true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Remove voice interactor service died", e);
+ return false;
+ }
+ }
+
+ /**
+ * Queries the supported commands available from the VoiceinteractionService.
+ * The command is a string that describes the generic operation to be performed.
+ * An example might be "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number
+ * of bags as part of airline check-in. (This is not an actual working example.)
+ *
+ * @param commands
+ */
+ public boolean[] supportsCommands(String[] commands) {
+ try {
+ boolean[] res = mInteractor.supportsCommands(mContext.getOpPackageName(), commands);
+ if (DEBUG) Log.d(TAG, "supportsCommands: cmds=" + commands + " res=" + res);
+ return res;
+ } catch (RemoteException e) {
+ throw new RuntimeException("Voice interactor has died", e);
+ }
+ }
+}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 3a766b7..58d707c 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -16,6 +16,7 @@
package android.app;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -41,14 +42,17 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.text.TextUtils;
import android.util.Log;
import android.view.WindowManagerGlobal;
import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -66,6 +70,11 @@ public class WallpaperManager {
private float mWallpaperXStep = -1;
private float mWallpaperYStep = -1;
+ /** {@hide} */
+ private static final String PROP_WALLPAPER = "ro.config.wallpaper";
+ /** {@hide} */
+ private static final String PROP_WALLPAPER_COMPONENT = "ro.config.wallpaper_component";
+
/**
* Activity Action: Show settings for choosing wallpaper. Do not use directly to construct
* an intent; instead, use {@link #getCropAndSetWallpaperIntent}.
@@ -219,24 +228,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 +239,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 +275,6 @@ public class WallpaperManager {
synchronized (this) {
mWallpaper = null;
mDefaultWallpaper = null;
- mHandler.removeMessages(MSG_CLEAR_WALLPAPER);
}
}
@@ -313,8 +309,7 @@ public class WallpaperManager {
}
private Bitmap getDefaultWallpaperLocked(Context context) {
- InputStream is = context.getResources().openRawResource(
- com.android.internal.R.drawable.default_wallpaper);
+ InputStream is = openDefaultWallpaper(context);
if (is != null) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
@@ -412,8 +407,7 @@ public class WallpaperManager {
horizontalAlignment = Math.max(0, Math.min(1, horizontalAlignment));
verticalAlignment = Math.max(0, Math.min(1, verticalAlignment));
- InputStream is = new BufferedInputStream(
- resources.openRawResource(com.android.internal.R.drawable.default_wallpaper));
+ InputStream is = new BufferedInputStream(openDefaultWallpaper(mContext));
if (is == null) {
Log.e(TAG, "default wallpaper input stream is null");
@@ -438,8 +432,7 @@ public class WallpaperManager {
}
}
- is = new BufferedInputStream(resources.openRawResource(
- com.android.internal.R.drawable.default_wallpaper));
+ is = new BufferedInputStream(openDefaultWallpaper(mContext));
RectF cropRectF;
@@ -488,8 +481,7 @@ public class WallpaperManager {
if (crop == null) {
// BitmapRegionDecoder has failed, try to crop in-memory
- is = new BufferedInputStream(resources.openRawResource(
- com.android.internal.R.drawable.default_wallpaper));
+ is = new BufferedInputStream(openDefaultWallpaper(mContext));
Bitmap fullSize = null;
if (is != null) {
BitmapFactory.Options options = new BitmapFactory.Options();
@@ -1022,6 +1014,53 @@ public class WallpaperManager {
* wallpaper.
*/
public void clear() throws IOException {
- setResource(com.android.internal.R.drawable.default_wallpaper);
+ setStream(openDefaultWallpaper(mContext));
+ }
+
+ /**
+ * Open stream representing the default static image wallpaper.
+ *
+ * @hide
+ */
+ public static InputStream openDefaultWallpaper(Context context) {
+ final String path = SystemProperties.get(PROP_WALLPAPER);
+ if (!TextUtils.isEmpty(path)) {
+ final File file = new File(path);
+ if (file.exists()) {
+ try {
+ return new FileInputStream(file);
+ } catch (IOException e) {
+ // Ignored, fall back to platform default below
+ }
+ }
+ }
+ return context.getResources().openRawResource(
+ com.android.internal.R.drawable.default_wallpaper);
+ }
+
+ /**
+ * Return {@link ComponentName} of the default live wallpaper, or
+ * {@code null} if none is defined.
+ *
+ * @hide
+ */
+ public static ComponentName getDefaultWallpaperComponent(Context context) {
+ String flat = SystemProperties.get(PROP_WALLPAPER_COMPONENT);
+ if (!TextUtils.isEmpty(flat)) {
+ final ComponentName cn = ComponentName.unflattenFromString(flat);
+ if (cn != null) {
+ return cn;
+ }
+ }
+
+ flat = context.getString(com.android.internal.R.string.default_wallpaper_component);
+ if (!TextUtils.isEmpty(flat)) {
+ final ComponentName cn = ComponentName.unflattenFromString(flat);
+ if (cn != null) {
+ return cn;
+ }
+ }
+
+ return null;
}
}
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..6f68dfb 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -22,14 +22,18 @@ 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.Bundle;
import android.os.Handler;
+import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.service.trust.TrustAgentService;
import android.util.Log;
import com.android.org.conscrypt.TrustedCertificateStore;
@@ -75,6 +79,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
@@ -131,6 +172,16 @@ public class DevicePolicyManager {
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_SET_NEW_PASSWORD
= "android.app.action.SET_NEW_PASSWORD";
+ /**
+ * Flag for {@link #forwardMatchingIntents}: the intents will forwarded to the primary user.
+ */
+ public static int FLAG_TO_PRIMARY_USER = 0x0001;
+
+ /**
+ * Flag for {@link #forwardMatchingIntents}: the intents will be forwarded to the managed
+ * profile.
+ */
+ public static int FLAG_TO_MANAGED_PROFILE = 0x0002;
/**
* Return true if the given administrator component is currently
@@ -1153,7 +1204,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) {
@@ -1225,7 +1278,7 @@ public class DevicePolicyManager {
public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0;
/**
- * Disable all keyguard widgets
+ * Disable all keyguard widgets. Has no effect.
*/
public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1 << 0;
@@ -1235,6 +1288,22 @@ public class DevicePolicyManager {
public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 1 << 1;
/**
+ * Disable showing all notifications on secure keyguard screens (e.g. PIN/Pattern/Password)
+ */
+ public static final int KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 1 << 2;
+
+ /**
+ * Only allow redacted notifications on secure keyguard screens (e.g. PIN/Pattern/Password)
+ */
+ public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 1 << 3;
+
+ /**
+ * Ignore {@link TrustAgentService} state on secure keyguard screens
+ * (e.g. PIN/Pattern/Password).
+ */
+ public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 1 << 4;
+
+ /**
* Disable all current and future keyguard customizations.
*/
public static final int KEYGUARD_DISABLE_FEATURES_ALL = 0x7fffffff;
@@ -1454,7 +1523,8 @@ public class DevicePolicyManager {
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param which {@link #KEYGUARD_DISABLE_FEATURES_NONE} (default),
* {@link #KEYGUARD_DISABLE_WIDGETS_ALL}, {@link #KEYGUARD_DISABLE_SECURE_CAMERA},
- * {@link #KEYGUARD_DISABLE_FEATURES_ALL}
+ * {@link #KEYGUARD_DISABLE_SECURE_NOTIFICATIONS}, {@link #KEYGUARD_DISABLE_TRUST_AGENTS},
+ * {@link #KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS}, {@link #KEYGUARD_DISABLE_FEATURES_ALL}
*/
public void setKeyguardDisabledFeatures(ComponentName admin, int which) {
if (mService != null) {
@@ -1493,10 +1563,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 +1574,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 +1758,287 @@ 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);
+ }
+ }
+ }
+
+ /**
+ * Called by a profile or device owner to set the application restrictions for a given target
+ * application running in the managed profile.
+ *
+ * <p>The provided {@link Bundle} consists of key-value pairs, where the types of values may be
+ * {@link Boolean}, {@link String}, or {@link String}[]. The recommended format for key strings
+ * is "com.example.packagename/example-setting" to avoid naming conflicts with library
+ * components such as {@link android.webkit.WebView}.
+ *
+ * <p>The application restrictions are only made visible to the target application and the
+ * profile or device owner.
+ *
+ * <p>The calling device admin must be a profile or device 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 to update restricted settings for.
+ * @param settings A {@link Bundle} to be parsed by the receiving application, conveying a new
+ * set of active restrictions.
+ */
+ public void setApplicationRestrictions(ComponentName admin, String packageName,
+ Bundle settings) {
+ if (mService != null) {
+ try {
+ mService.setApplicationRestrictions(admin, packageName, settings);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Called by a profile owner to forward intents sent from the managed profile to the owner, or
+ * from the owner to the managed profile.
+ * If an intent matches this intent filter, then activities belonging to the other user can
+ * respond to this intent.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param filter if an intent matches this IntentFilter, then it can be forwarded.
+ */
+ public void forwardMatchingIntents(ComponentName admin, IntentFilter filter, int flags) {
+ if (mService != null) {
+ try {
+ mService.forwardMatchingIntents(admin, filter, flags);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Called by a profile owner to remove all the forwarding intent filters from the current user
+ * and from the owner.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ */
+ public void clearForwardingIntentFilters(ComponentName admin) {
+ if (mService != null) {
+ try {
+ mService.clearForwardingIntentFilters(admin);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Called by a profile or device owner to get the application restrictions for a given target
+ * application running in the managed profile.
+ *
+ * <p>The calling device admin must be a profile or device 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 to fetch restricted settings of.
+ * @return {@link Bundle} of settings corresponding to what was set last time
+ * {@link DevicePolicyManager#setApplicationRestrictions} was called, or an empty {@link Bundle}
+ * if no restrictions have been set.
+ */
+ public Bundle getApplicationRestrictions(ComponentName admin, String packageName) {
+ if (mService != null) {
+ try {
+ return mService.getApplicationRestrictions(admin, packageName);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Called by a profile or device owner to set a user restriction specified
+ * by the key.
+ * <p>
+ * The calling device admin must be a profile or device owner; if it is not,
+ * a security exception will be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated
+ * with.
+ * @param key The key of the restriction. See the constants in
+ * {@link android.os.UserManager} for the list of keys.
+ */
+ public void addUserRestriction(ComponentName admin, String key) {
+ if (mService != null) {
+ try {
+ mService.setUserRestriction(admin, key, true);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Called by a profile or device owner to clear a user restriction specified
+ * by the key.
+ * <p>
+ * The calling device admin must be a profile or device owner; if it is not,
+ * a security exception will be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated
+ * with.
+ * @param key The key of the restriction. See the constants in
+ * {@link android.os.UserManager} for the list of keys.
+ */
+ public void clearUserRestriction(ComponentName admin, String key) {
+ if (mService != null) {
+ try {
+ mService.setUserRestriction(admin, key, false);
+ } 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..495a5f9 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -18,6 +18,8 @@
package android.app.admin;
import android.content.ComponentName;
+import android.content.IntentFilter;
+import android.os.Bundle;
import android.os.RemoteCallback;
/**
@@ -103,6 +105,21 @@ 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);
+
+ void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings);
+ Bundle getApplicationRestrictions(in ComponentName who, in String packageName);
+
+ void setUserRestriction(in ComponentName who, in String key, boolean enable);
+ void forwardMatchingIntents(in ComponentName admin, in IntentFilter filter, int flags);
+ void clearForwardingIntentFilters(in ComponentName admin);
}
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 70a3797..886f1a6 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -127,6 +127,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);
@@ -139,12 +146,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 */ }
@@ -679,5 +683,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 477285d..6ebb6c4 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.system.ErrnoException;
import android.system.Os;
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/app/task/ITaskCallback.aidl b/core/java/android/app/task/ITaskCallback.aidl
new file mode 100644
index 0000000..ffa57d1
--- /dev/null
+++ b/core/java/android/app/task/ITaskCallback.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.task;
+
+import android.app.task.ITaskService;
+import android.app.task.TaskParams;
+
+/**
+ * The server side of the TaskManager 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 Task Service instance is reporting.
+ *
+ * {@hide}
+ */
+interface ITaskCallback {
+ /**
+ * Immediate callback to the system after sending a start signal, used to quickly detect ANR.
+ *
+ * @param taskId Unique integer used to identify this task.
+ */
+ void acknowledgeStartMessage(int taskId);
+ /**
+ * Immediate callback to the system after sending a stop signal, used to quickly detect ANR.
+ *
+ * @param taskId Unique integer used to identify this task.
+ */
+ void acknowledgeStopMessage(int taskId);
+ /*
+ * Tell the task manager that the client is done with its execution, so that it can go on to
+ * the next one and stop attributing wakelock time to us etc.
+ *
+ * @param taskId Unique integer used to identify this task.
+ * @param reschedule Whether or not to reschedule this task.
+ */
+ void taskFinished(int taskId, boolean reschedule);
+}
diff --git a/core/java/android/app/task/ITaskService.aidl b/core/java/android/app/task/ITaskService.aidl
new file mode 100644
index 0000000..87b0191
--- /dev/null
+++ b/core/java/android/app/task/ITaskService.aidl
@@ -0,0 +1,35 @@
+/**
+ * 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.task;
+
+import android.app.task.ITaskCallback;
+import android.app.task.TaskParams;
+
+import android.os.Bundle;
+
+/**
+ * Interface that the framework uses to communicate with application code that implements a
+ * TaskService. End user code does not implement this interface directly; instead, the app's
+ * service implementation will extend android.app.task.TaskService.
+ * {@hide}
+ */
+oneway interface ITaskService {
+ /** Begin execution of application's task. */
+ void startTask(in TaskParams taskParams);
+ /** Stop execution of application's task. */
+ void stopTask(in TaskParams taskParams);
+}
diff --git a/core/java/android/app/task/TaskParams.aidl b/core/java/android/app/task/TaskParams.aidl
new file mode 100644
index 0000000..9b25855
--- /dev/null
+++ b/core/java/android/app/task/TaskParams.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.task;
+
+parcelable TaskParams; \ No newline at end of file
diff --git a/core/java/android/app/task/TaskParams.java b/core/java/android/app/task/TaskParams.java
new file mode 100644
index 0000000..e2eafd8
--- /dev/null
+++ b/core/java/android/app/task/TaskParams.java
@@ -0,0 +1,86 @@
+/*
+ * 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.task;
+
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Contains the parameters used to configure/identify your task. You do not create this object
+ * yourself, instead it is handed in to your application by the System.
+ */
+public class TaskParams implements Parcelable {
+
+ private final int taskId;
+ private final Bundle extras;
+ private final IBinder mCallback;
+
+ /**
+ * @return The unique id of this task, specified at creation time.
+ */
+ public int getTaskId() {
+ return taskId;
+ }
+
+ /**
+ * @return The extras you passed in when constructing this task with
+ * {@link android.content.Task.Builder#setExtras(android.os.Bundle)}. This will
+ * never be null. If you did not set any extras this will be an empty bundle.
+ */
+ public Bundle getExtras() {
+ return extras;
+ }
+
+ /**
+ * @hide
+ */
+ public ITaskCallback getCallback() {
+ return ITaskCallback.Stub.asInterface(mCallback);
+ }
+
+ private TaskParams(Parcel in) {
+ taskId = in.readInt();
+ extras = in.readBundle();
+ mCallback = in.readStrongBinder();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(taskId);
+ dest.writeBundle(extras);
+ dest.writeStrongBinder(mCallback);
+ }
+
+ public static final Creator<TaskParams> CREATOR = new Creator<TaskParams>() {
+ @Override
+ public TaskParams createFromParcel(Parcel in) {
+ return new TaskParams(in);
+ }
+
+ @Override
+ public TaskParams[] newArray(int size) {
+ return new TaskParams[size];
+ }
+ };
+}
diff --git a/core/java/android/app/task/TaskService.java b/core/java/android/app/task/TaskService.java
new file mode 100644
index 0000000..81333be
--- /dev/null
+++ b/core/java/android/app/task/TaskService.java
@@ -0,0 +1,260 @@
+/*
+ * 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.task;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * <p>Entry point for the callback from the {@link android.content.TaskManager}.</p>
+ * <p>This is the base class that handles asynchronous requests that were previously scheduled. You
+ * are responsible for overriding {@link TaskService#onStartTask(TaskParams)}, which is where
+ * you will implement your task logic.</p>
+ * <p>This service executes each incoming task on a {@link android.os.Handler} running on your
+ * application's main thread. This means that you <b>must</b> offload your execution logic to
+ * another thread/handler/{@link android.os.AsyncTask} of your choosing. Not doing so will result
+ * in blocking any future callbacks from the TaskManager - specifically
+ * {@link #onStopTask(android.app.task.TaskParams)}, which is meant to inform you that the
+ * scheduling requirements are no longer being met.</p>
+ */
+public abstract class TaskService extends Service {
+ private static final String TAG = "TaskService";
+
+ /**
+ * Task services must be protected with this permission:
+ *
+ * <pre class="prettyprint">
+ * <service android:name="MyTaskService"
+ * android:permission="android.permission.BIND_TASK_SERVICE" >
+ * ...
+ * </service>
+ * </pre>
+ *
+ * <p>If a task 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_TASK_SERVICE";
+
+ /**
+ * Identifier for a message that will result in a call to
+ * {@link #onStartTask(android.app.task.TaskParams)}.
+ */
+ private final int MSG_EXECUTE_TASK = 0;
+ /**
+ * Message that will result in a call to {@link #onStopTask(android.app.task.TaskParams)}.
+ */
+ private final int MSG_STOP_TASK = 1;
+ /**
+ * Message that the client has completed execution of this task.
+ */
+ private final int MSG_TASK_FINISHED = 2;
+
+ /** Lock object for {@link #mHandler}. */
+ private final Object mHandlerLock = new Object();
+
+ /**
+ * Handler we post tasks to. Responsible for calling into the client logic, and handling the
+ * callback to the system.
+ */
+ @GuardedBy("mHandlerLock")
+ TaskHandler mHandler;
+
+ /** Binder for this service. */
+ ITaskService mBinder = new ITaskService.Stub() {
+ @Override
+ public void startTask(TaskParams taskParams) {
+ ensureHandler();
+ Message m = Message.obtain(mHandler, MSG_EXECUTE_TASK, taskParams);
+ m.sendToTarget();
+ }
+ @Override
+ public void stopTask(TaskParams taskParams) {
+ ensureHandler();
+ Message m = Message.obtain(mHandler, MSG_STOP_TASK, taskParams);
+ m.sendToTarget();
+ }
+ };
+
+ /** @hide */
+ void ensureHandler() {
+ synchronized (mHandlerLock) {
+ if (mHandler == null) {
+ mHandler = new TaskHandler(getMainLooper());
+ }
+ }
+ }
+
+ /**
+ * Runs on application's main thread - callbacks are meant to offboard work to some other
+ * (app-specified) mechanism.
+ * @hide
+ */
+ class TaskHandler extends Handler {
+ TaskHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final TaskParams params = (TaskParams) msg.obj;
+ switch (msg.what) {
+ case MSG_EXECUTE_TASK:
+ try {
+ TaskService.this.onStartTask(params);
+ } catch (Exception e) {
+ Log.e(TAG, "Error while executing task: " + params.getTaskId());
+ throw new RuntimeException(e);
+ } finally {
+ maybeAckMessageReceived(params, MSG_EXECUTE_TASK);
+ }
+ break;
+ case MSG_STOP_TASK:
+ try {
+ TaskService.this.onStopTask(params);
+ } catch (Exception e) {
+ Log.e(TAG, "Application unable to handle onStopTask.", e);
+ throw new RuntimeException(e);
+ } finally {
+ maybeAckMessageReceived(params, MSG_STOP_TASK);
+ }
+ break;
+ case MSG_TASK_FINISHED:
+ final boolean needsReschedule = (msg.arg2 == 1);
+ ITaskCallback callback = params.getCallback();
+ if (callback != null) {
+ try {
+ callback.taskFinished(params.getTaskId(), needsReschedule);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error reporting task finish to system: binder has gone" +
+ "away.");
+ }
+ } else {
+ Log.e(TAG, "finishTask() called for a nonexistent task id.");
+ }
+ break;
+ default:
+ Log.e(TAG, "Unrecognised message received.");
+ break;
+ }
+ }
+
+ /**
+ * Messages come in on the application's main thread, so rather than run the risk of
+ * waiting for an app that may be doing something foolhardy, we ack to the system after
+ * processing a message. This allows us to throw up an ANR dialogue as quickly as possible.
+ * @param params id of the task we're acking.
+ * @param state Information about what message we're acking.
+ */
+ private void maybeAckMessageReceived(TaskParams params, int state) {
+ final ITaskCallback callback = params.getCallback();
+ final int taskId = params.getTaskId();
+ if (callback != null) {
+ try {
+ if (state == MSG_EXECUTE_TASK) {
+ callback.acknowledgeStartMessage(taskId);
+ } else if (state == MSG_STOP_TASK) {
+ callback.acknowledgeStopMessage(taskId);
+ }
+ } catch(RemoteException e) {
+ Log.e(TAG, "System unreachable for starting task.");
+ }
+ } else {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, state + ": Attempting to ack a task that has already been" +
+ "processed.");
+ }
+ }
+ }
+ }
+
+ /** @hide */
+ public final IBinder onBind(Intent intent) {
+ return mBinder.asBinder();
+ }
+
+ /**
+ * Override this method with the callback logic for your task. Any such logic needs to be
+ * performed on a separate thread, as this function is executed on your application's main
+ * thread.
+ *
+ * @param params Parameters specifying info about this task, including the extras bundle you
+ * optionally provided at task-creation time.
+ */
+ public abstract void onStartTask(TaskParams params);
+
+ /**
+ * This method is called if your task should be stopped even before you've called
+ * {@link #taskFinished(TaskParams, boolean)}.
+ *
+ * <p>This will happen if the requirements specified at schedule time are no longer met. For
+ * example you may have requested WiFi with
+ * {@link android.content.Task.Builder#setRequiredNetworkCapabilities(int)}, yet while your
+ * task was executing the user toggled WiFi. Another example is if you had specified
+ * {@link android.content.Task.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
+ * idle maintenance window. You are solely responsible for the behaviour of your application
+ * upon receipt of this message; your app will likely start to misbehave if you ignore it. One
+ * repercussion is that the system will cease to hold a wakelock for you.</p>
+ *
+ * <p>After you've done your clean-up you are still expected to call
+ * {@link #taskFinished(TaskParams, boolean)} this will inform the TaskManager that all is well, and
+ * allow you to reschedule your task as it is probably uncompleted. Until you call
+ * taskFinished() you will not receive any newly scheduled tasks with the given task id as the
+ * TaskManager will consider the task to be in an error state.</p>
+ *
+ * @param params Parameters specifying info about this task.
+ * @return True to indicate to the TaskManager whether you'd like to reschedule this task based
+ * on the criteria provided at task creation-time. False to drop the task. Regardless of the
+ * value returned, your task must stop executing.
+ */
+ public abstract boolean onStopTask(TaskParams params);
+
+ /**
+ * Callback to inform the TaskManager you have completed execution. This can be called from any
+ * thread, as it will ultimately be run on your application's main thread. When the system
+ * receives this message it will release the wakelock being held.
+ * <p>
+ * You can specify post-execution behaviour to the scheduler here with <code>needsReschedule
+ * </code>. This will apply a back-off timer to your task based on the default, or what was
+ * set with {@link android.content.Task.Builder#setBackoffCriteria(long, int)}. The
+ * original requirements are always honoured even for a backed-off task.
+ * Note that a task running in idle mode will not be backed-off. Instead what will happen
+ * is the task will be re-added to the queue and re-executed within a future idle
+ * maintenance window.
+ * </p>
+ *
+ * @param params Parameters specifying system-provided info about this task, this was given to
+ * your application in {@link #onStartTask(TaskParams)}.
+ * @param needsReschedule True if this task is complete, false if you want the TaskManager to
+ * reschedule you.
+ */
+ public final void taskFinished(TaskParams params, boolean needsReschedule) {
+ ensureHandler();
+ Message m = Message.obtain(mHandler, MSG_TASK_FINISHED, params);
+ m.arg2 = needsReschedule ? 1 : 0;
+ m.sendToTarget();
+ }
+} \ No newline at end of file
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 7745bb7..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;
@@ -61,9 +53,6 @@ public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker {
private static final boolean DBG = true;
private static final boolean VDBG = true;
- // Event sent to the mBtdtHandler when DHCP fails so we can tear down the network.
- private static final int EVENT_NETWORK_FAILED = 1;
-
private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
private AtomicInteger mDefaultGatewayAddr = new AtomicInteger(0);
@@ -146,11 +135,6 @@ public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker {
}
@Override
- public void captivePortalCheckComplete() {
- // not implemented
- }
-
- @Override
public void captivePortalCheckCompleted(boolean isCaptivePortal) {
// not implemented
}
@@ -331,7 +315,6 @@ public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker {
}
if (!success) {
Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError());
- mBtdtHandler.obtainMessage(EVENT_NETWORK_FAILED).sendToTarget();
return;
}
mLinkProperties = dhcpResults.linkProperties;
@@ -424,10 +407,6 @@ public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker {
if (VDBG) Log.d(TAG, "got EVENT_NETWORK_DISCONNECTED, " + linkProperties);
mBtdt.stopReverseTether();
break;
- case EVENT_NETWORK_FAILED:
- if (VDBG) Log.d(TAG, "got EVENT_NETWORK_FAILED");
- mBtdt.teardown();
- break;
}
}
}
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..5b41394 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>
@@ -1636,7 +1641,7 @@ public abstract class ContentResolver {
*
* @see #getPersistedUriPermissions()
*/
- public void takePersistableUriPermission(Uri uri, int modeFlags) {
+ public void takePersistableUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags) {
try {
ActivityManagerNative.getDefault().takePersistableUriPermission(uri, modeFlags);
} catch (RemoteException e) {
@@ -1651,7 +1656,7 @@ public abstract class ContentResolver {
*
* @see #getPersistedUriPermissions()
*/
- public void releasePersistableUriPermission(Uri uri, int modeFlags) {
+ public void releasePersistableUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags) {
try {
ActivityManagerNative.getDefault().releasePersistableUriPermission(uri, modeFlags);
} catch (RemoteException e) {
@@ -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..de223a3 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.SessionManager} for managing media Sessions.
+ *
+ * @see #getSystemService
+ * @see android.media.session.SessionManager
+ */
+ 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.
*
@@ -2307,6 +2442,14 @@ public abstract class Context {
public static final String APPWIDGET_SERVICE = "appwidget";
/**
+ * Official published name of the (internal) voice interaction manager service.
+ *
+ * @hide
+ * @see #getSystemService
+ */
+ public static final String VOICE_INTERACTION_MANAGER_SERVICE = "voiceinteraction";
+
+ /**
* Use with {@link #getSystemService} to retrieve an
* {@link android.app.backup.IBackupManager IBackupManager} for communicating
* with the backup mechanism.
@@ -2351,6 +2494,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 +2585,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 +2610,6 @@ public abstract class Context {
*
* @see #getSystemService
* @see android.hardware.camera2.CameraManager
- * @hide
*/
public static final String CAMERA_SERVICE = "camera";
@@ -2473,6 +2633,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 +2674,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 +2698,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 +2717,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 +2733,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 +2754,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 +2770,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
@@ -2610,14 +2799,18 @@ public abstract class Context {
* @param uri The Uri you would like to grant access to.
* @param modeFlags The desired access modes. Any combination of
* {@link Intent#FLAG_GRANT_READ_URI_PERMISSION
- * Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+ * Intent.FLAG_GRANT_READ_URI_PERMISSION},
* {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION
- * Intent.FLAG_GRANT_WRITE_URI_PERMISSION}.
+ * Intent.FLAG_GRANT_WRITE_URI_PERMISSION},
+ * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ * Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION}, or
+ * {@link Intent#FLAG_GRANT_PREFIX_URI_PERMISSION
+ * Intent.FLAG_GRANT_PREFIX_URI_PERMISSION}.
*
* @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
@@ -2625,7 +2818,8 @@ public abstract class Context {
* Uri will match all previously granted Uris that are the same or a
* sub-path of the given Uri. That is, revoking "content://foo/target" will
* revoke both "content://foo/target" and "content://foo/target/sub", but not
- * "content://foo".
+ * "content://foo". It will not remove any prefix grants that exist at a
+ * higher level.
*
* @param uri The Uri you would like to revoke access to.
* @param modeFlags The desired access modes. Any combination of
@@ -2636,7 +2830,7 @@ public abstract class Context {
*
* @see #grantUriPermission
*/
- public abstract void revokeUriPermission(Uri uri, int modeFlags);
+ public abstract void revokeUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags);
/**
* Determine whether a particular process and user ID has been granted
@@ -2659,7 +2853,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.AccessUriMode int modeFlags);
/**
* Determine whether the calling process and user ID has been
@@ -2682,7 +2877,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.AccessUriMode int modeFlags);
/**
* Determine whether the calling process of an IPC <em>or you</em> has been granted
@@ -2701,7 +2896,8 @@ public abstract class Context {
*
* @see #checkCallingUriPermission
*/
- public abstract int checkCallingOrSelfUriPermission(Uri uri, int modeFlags);
+ public abstract int checkCallingOrSelfUriPermission(Uri uri,
+ @Intent.AccessUriMode int modeFlags);
/**
* Check both a Uri and normal permission. This allows you to perform
@@ -2713,7 +2909,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 +2921,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.AccessUriMode int modeFlags);
/**
* If a particular process and user ID has not been granted
@@ -2748,7 +2945,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.AccessUriMode int modeFlags, String message);
/**
* If the calling process and user ID has not been granted
@@ -2770,7 +2967,7 @@ public abstract class Context {
* @see #checkCallingUriPermission(Uri, int)
*/
public abstract void enforceCallingUriPermission(
- Uri uri, int modeFlags, String message);
+ Uri uri, @Intent.AccessUriMode int modeFlags, String message);
/**
* If the calling process of an IPC <em>or you</em> has not been
@@ -2789,7 +2986,7 @@ public abstract class Context {
* @see #checkCallingOrSelfUriPermission(Uri, int)
*/
public abstract void enforceCallingOrSelfUriPermission(
- Uri uri, int modeFlags, String message);
+ Uri uri, @Intent.AccessUriMode int modeFlags, String message);
/**
* Enforce both a Uri and normal permission. This allows you to perform
@@ -2801,7 +2998,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 +3010,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.AccessUriMode 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 +3076,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 +3112,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 +3133,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..ae5437b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -18,9 +18,11 @@ package android.content;
import android.content.pm.ApplicationInfo;
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 +47,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;
@@ -861,8 +865,9 @@ public class Intent implements Parcelable, Cloneable {
}
// Migrate any clip data and flags from target.
- int permFlags = target.getFlags()
- & (FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION);
+ int permFlags = target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION
+ | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | FLAG_GRANT_PREFIX_URI_PERMISSION);
if (permFlags != 0) {
ClipData targetClipData = target.getClipData();
if (targetClipData == null && target.getData() != null) {
@@ -2193,6 +2198,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>
@@ -2298,6 +2308,16 @@ public class Intent implements Parcelable, Cloneable {
= "android.intent.action.ADVANCED_SETTINGS";
/**
+ * Broadcast Action: Sent after application restrictions are changed.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_APPLICATION_RESTRICTIONS_CHANGED =
+ "android.intent.action.APPLICATION_RESTRICTIONS_CHANGED";
+
+ /**
* Broadcast Action: An outgoing call is about to be placed.
*
* <p>The Intent will have the following extra value:</p>
@@ -2623,6 +2643,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 =
@@ -2663,9 +2700,11 @@ public class Intent implements Parcelable, Cloneable {
* take the persistable permissions using
* {@link ContentResolver#takePersistableUriPermission(Uri, int)}.
* <p>
- * Callers can restrict document selection to a specific kind of data, such
- * as photos, by setting one or more MIME types in
- * {@link #EXTRA_MIME_TYPES}.
+ * Callers must indicate the acceptable document MIME types through
+ * {@link #setType(String)}. For example, to select photos, use
+ * {@code image/*}. If multiple disjoint MIME types are acceptable, define
+ * them in {@link #EXTRA_MIME_TYPES} and {@link #setType(String)} to
+ * {@literal *}/*.
* <p>
* If the caller can handle multiple returned items (the user performing
* multiple selection), then you can specify {@link #EXTRA_ALLOW_MULTIPLE}
@@ -2675,9 +2714,10 @@ public class Intent implements Parcelable, Cloneable {
* returned URIs can be opened with
* {@link ContentResolver#openFileDescriptor(Uri, String)}.
* <p>
- * Output: The URI of the item that was picked. This must be a
- * {@code content://} URI so that any receiver can access it. If multiple
- * documents were selected, they are returned in {@link #getClipData()}.
+ * Output: The URI of the item that was picked, returned in
+ * {@link #getData()}. This must be a {@code content://} URI so that any
+ * receiver can access it. If multiple documents were selected, they are
+ * returned in {@link #getClipData()}.
*
* @see DocumentsContract
* @see #ACTION_CREATE_DOCUMENT
@@ -2719,6 +2759,24 @@ public class Intent implements Parcelable, Cloneable {
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
+ /**
+ * Activity Action: Allow the user to pick a directory. When invoked, the
+ * system will display the various {@link DocumentsProvider} instances
+ * installed on the device, letting the user navigate through them. Apps can
+ * fully manage documents within the returned directory.
+ * <p>
+ * To gain access to descendant (child, grandchild, etc) documents, use
+ * {@link DocumentsContract#buildDocumentViaUri(Uri, String)} and
+ * {@link DocumentsContract#buildChildDocumentsViaUri(Uri, String)} using
+ * the returned directory URI.
+ * <p>
+ * Output: The URI representing the selected directory.
+ *
+ * @see DocumentsContract
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PICK_DIRECTORY = "android.intent.action.PICK_DIRECTORY";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
@@ -2746,6 +2804,14 @@ public class Intent implements Parcelable, Cloneable {
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_BROWSABLE = "android.intent.category.BROWSABLE";
/**
+ * Categories for activities that can participate in voice interaction.
+ * An activity that supports this category must be prepared to run with
+ * no UI shown at all (though in some case it may have a UI shown), and
+ * rely on {@link android.app.VoiceInteractor} to interact with the user.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_VOICE = "android.intent.category.VOICE";
+ /**
* Set if the activity should be considered as an alternative action to
* the data the user is currently viewing. See also
* {@link #CATEGORY_SELECTED_ALTERNATIVE} for an alternative action that
@@ -2899,6 +2965,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()).
@@ -3289,6 +3363,7 @@ public class Intent implements Parcelable, Cloneable {
* @see #ACTION_GET_CONTENT
* @see #ACTION_OPEN_DOCUMENT
* @see #ACTION_CREATE_DOCUMENT
+ * @see #ACTION_PICK_DIRECTORY
*/
public static final String EXTRA_LOCAL_ONLY =
"android.intent.extra.LOCAL_ONLY";
@@ -3308,15 +3383,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 +3456,30 @@ 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,
+ FLAG_GRANT_PERSISTABLE_URI_PERMISSION, FLAG_GRANT_PREFIX_URI_PERMISSION })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface GrantUriMode {}
+
+ /** @hide */
+ @IntDef(flag = true, value = {
+ FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AccessUriMode {}
+
+ /**
+ * Test if given mode flags specify an access mode, which must be at least
+ * read and/or write.
+ *
+ * @hide
+ */
+ public static boolean isAccessUriMode(int modeFlags) {
+ return (modeFlags & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) != 0;
+ }
+
/**
* 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
@@ -3434,6 +3541,17 @@ public class Intent implements Parcelable, Cloneable {
public static final int FLAG_GRANT_PERSISTABLE_URI_PERMISSION = 0x00000040;
/**
+ * When combined with {@link #FLAG_GRANT_READ_URI_PERMISSION} and/or
+ * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, the URI permission grant
+ * applies to any URI that is a prefix match against the original granted
+ * URI. (Without this flag, the URI must match exactly for access to be
+ * granted.) Another URI is considered a prefix match only when scheme,
+ * authority, and all path segments defined by the prefix are an exact
+ * match.
+ */
+ public static final int FLAG_GRANT_PREFIX_URI_PERMISSION = 0x00000080;
+
+ /**
* If set, the new activity is not kept in the history stack. As soon as
* the user navigates away from it, the activity is finished. This may also
* be set with the {@link android.R.styleable#AndroidManifestActivity_noHistory
@@ -3471,7 +3589,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 +3610,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 +3728,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.
@@ -3710,9 +3871,9 @@ public class Intent implements Parcelable, Cloneable {
/**
* @hide Flags that can't be changed with PendingIntent.
*/
- public static final int IMMUTABLE_FLAGS =
- FLAG_GRANT_READ_URI_PERMISSION
- | FLAG_GRANT_WRITE_URI_PERMISSION;
+ public static final int IMMUTABLE_FLAGS = FLAG_GRANT_READ_URI_PERMISSION
+ | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | FLAG_GRANT_PREFIX_URI_PERMISSION;
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
@@ -6250,6 +6411,8 @@ public class Intent implements Parcelable, Cloneable {
*
* @see #FLAG_GRANT_READ_URI_PERMISSION
* @see #FLAG_GRANT_WRITE_URI_PERMISSION
+ * @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ * @see #FLAG_GRANT_PREFIX_URI_PERMISSION
* @see #FLAG_DEBUG_LOG_RESOLUTION
* @see #FLAG_FROM_BACKGROUND
* @see #FLAG_ACTIVITY_BROUGHT_TO_FRONT
@@ -6260,6 +6423,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 +6581,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 +6689,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)) {
@@ -7263,9 +7444,10 @@ public class Intent implements Parcelable, Cloneable {
// Since we migrated in child, we need to promote ClipData
// and flags to ourselves to grant.
setClipData(target.getClipData());
- addFlags(target.getFlags()
- & (FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION
- | FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
+ addFlags(target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION
+ | FLAG_GRANT_WRITE_URI_PERMISSION
+ | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | FLAG_GRANT_PREFIX_URI_PERMISSION));
return true;
} else {
return false;
@@ -7339,4 +7521,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/Task.java b/core/java/android/content/Task.java
new file mode 100644
index 0000000..ed5ed88
--- /dev/null
+++ b/core/java/android/content/Task.java
@@ -0,0 +1,400 @@
+/*
+ * 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;
+
+import android.app.task.TaskService;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Container of data passed to the {@link android.content.TaskManager} fully encapsulating the
+ * parameters required to schedule work against the calling application. These are constructed
+ * using the {@link Task.Builder}.
+ */
+public class Task implements Parcelable {
+
+ public interface NetworkType {
+ public final int ANY = 0;
+ public final int UNMETERED = 1;
+ }
+
+ /**
+ * Linear: retry_time(failure_time, t) = failure_time + initial_retry_delay * t, t >= 1
+ * Expon: retry_time(failure_time, t) = failure_time + initial_retry_delay ^ t, t >= 1
+ */
+ public interface BackoffPolicy {
+ public final int LINEAR = 0;
+ public final int EXPONENTIAL = 1;
+ }
+
+ /**
+ * Unique task id associated with this class. This is assigned to your task by the scheduler.
+ */
+ public int getTaskId() {
+ return taskId;
+ }
+
+ /**
+ * Bundle of extras which are returned to your application at execution time.
+ */
+ public Bundle getExtras() {
+ return extras;
+ }
+
+ /**
+ * Name of the service endpoint that will be called back into by the TaskManager.
+ */
+ public String getServiceClassName() {
+ return serviceClassName;
+ }
+
+ /**
+ * Whether this task needs the device to be plugged in.
+ */
+ public boolean isRequireCharging() {
+ return requireCharging;
+ }
+
+ /**
+ * Whether this task needs the device to be in an Idle maintenance window.
+ */
+ public boolean isRequireDeviceIdle() {
+ return requireDeviceIdle;
+ }
+
+ /**
+ * See {@link android.content.Task.NetworkType} for a description of this value.
+ */
+ public int getNetworkCapabilities() {
+ return networkCapabilities;
+ }
+
+ /**
+ * Set for a task that does not recur periodically, to specify a delay after which the task
+ * will be eligible for execution. This value is not set if the task recurs periodically.
+ */
+ public long getMinLatencyMillis() {
+ return minLatencyMillis;
+ }
+
+ /**
+ * See {@link Builder#setOverrideDeadline(long)}. This value is not set if the task recurs
+ * periodically.
+ */
+ public long getMaxExecutionDelayMillis() {
+ return maxExecutionDelayMillis;
+ }
+
+ /**
+ * Track whether this task will repeat with a given period.
+ */
+ public boolean isPeriodic() {
+ return isPeriodic;
+ }
+
+ /**
+ * Set to the interval between occurrences of this task. This value is <b>not</b> set if the
+ * task does not recur periodically.
+ */
+ public long getIntervalMillis() {
+ return intervalMillis;
+ }
+
+ /**
+ * The amount of time the TaskManager will wait before rescheduling a failed task. This value
+ * will be increased depending on the backoff policy specified at task creation time. Defaults
+ * to 5 seconds.
+ */
+ public long getInitialBackoffMillis() {
+ return initialBackoffMillis;
+ }
+
+ /**
+ * See {@link android.content.Task.BackoffPolicy} for an explanation of the values this field
+ * can take. This defaults to exponential.
+ */
+ public int getBackoffPolicy() {
+ return backoffPolicy;
+ }
+
+ private final int taskId;
+ // TODO: Change this to use PersistableBundle when that lands in master.
+ private final Bundle extras;
+ private final String serviceClassName;
+ private final boolean requireCharging;
+ private final boolean requireDeviceIdle;
+ private final int networkCapabilities;
+ private final long minLatencyMillis;
+ private final long maxExecutionDelayMillis;
+ private final boolean isPeriodic;
+ private final long intervalMillis;
+ private final long initialBackoffMillis;
+ private final int backoffPolicy;
+
+ private Task(Parcel in) {
+ taskId = in.readInt();
+ extras = in.readBundle();
+ serviceClassName = in.readString();
+ requireCharging = in.readInt() == 1;
+ requireDeviceIdle = in.readInt() == 1;
+ networkCapabilities = in.readInt();
+ minLatencyMillis = in.readLong();
+ maxExecutionDelayMillis = in.readLong();
+ isPeriodic = in.readInt() == 1;
+ intervalMillis = in.readLong();
+ initialBackoffMillis = in.readLong();
+ backoffPolicy = in.readInt();
+ }
+
+ private Task(Task.Builder b) {
+ taskId = b.mTaskId;
+ extras = new Bundle(b.mExtras);
+ serviceClassName = b.mTaskServiceClassName;
+ requireCharging = b.mRequiresCharging;
+ requireDeviceIdle = b.mRequiresDeviceIdle;
+ networkCapabilities = b.mNetworkCapabilities;
+ minLatencyMillis = b.mMinLatencyMillis;
+ maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
+ isPeriodic = b.mIsPeriodic;
+ intervalMillis = b.mIntervalMillis;
+ initialBackoffMillis = b.mInitialBackoffMillis;
+ backoffPolicy = b.mBackoffPolicy;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(taskId);
+ out.writeBundle(extras);
+ out.writeString(serviceClassName);
+ out.writeInt(requireCharging ? 1 : 0);
+ out.writeInt(requireDeviceIdle ? 1 : 0);
+ out.writeInt(networkCapabilities);
+ out.writeLong(minLatencyMillis);
+ out.writeLong(maxExecutionDelayMillis);
+ out.writeInt(isPeriodic ? 1 : 0);
+ out.writeLong(intervalMillis);
+ out.writeLong(initialBackoffMillis);
+ out.writeInt(backoffPolicy);
+ }
+
+ public static final Creator<Task> CREATOR = new Creator<Task>() {
+ @Override
+ public Task createFromParcel(Parcel in) {
+ return new Task(in);
+ }
+
+ @Override
+ public Task[] newArray(int size) {
+ return new Task[size];
+ }
+ };
+
+ /**
+ * Builder class for constructing {@link Task} objects.
+ */
+ public final class Builder {
+ private int mTaskId;
+ private Bundle mExtras;
+ private String mTaskServiceClassName;
+ // Requirements.
+ private boolean mRequiresCharging;
+ private boolean mRequiresDeviceIdle;
+ private int mNetworkCapabilities;
+ // One-off parameters.
+ private long mMinLatencyMillis;
+ private long mMaxExecutionDelayMillis;
+ // Periodic parameters.
+ private boolean mIsPeriodic;
+ private long mIntervalMillis;
+ // Back-off parameters.
+ private long mInitialBackoffMillis = 5000L;
+ private int mBackoffPolicy = BackoffPolicy.EXPONENTIAL;
+ /** Easy way to track whether the client has tried to set a back-off policy. */
+ private boolean mBackoffPolicySet = false;
+
+ /**
+ * @param taskId Application-provided id for this task. Subsequent calls to cancel, or
+ * tasks created with the same taskId, will update the pre-existing task with
+ * the same id.
+ * @param cls The endpoint that you implement that will receive the callback from the
+ * TaskManager.
+ */
+ public Builder(int taskId, Class<TaskService> cls) {
+ mTaskServiceClassName = cls.getClass().getName();
+ mTaskId = taskId;
+ }
+
+ /**
+ * Set optional extras. This is persisted, so we only allow primitive types.
+ * @param extras Bundle containing extras you want the scheduler to hold on to for you.
+ */
+ public Builder setExtras(Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Set some description of the kind of network capabilities you would like to have. This
+ * will be a parameter defined in {@link android.content.Task.NetworkType}.
+ * Not calling this function means the network is not necessary.
+ * Bear in mind that calling this function defines network as a strict requirement for your
+ * task if the network requested is not available your task will never run. See
+ * {@link #setOverrideDeadline(long)} to change this behaviour.
+ */
+ public Builder setRequiredNetworkCapabilities(int networkCapabilities) {
+ mNetworkCapabilities = networkCapabilities;
+ return this;
+ }
+
+ /*
+ * Specify that to run this task, the device needs to be plugged in. This defaults to
+ * false.
+ * @param requireCharging Whether or not the device is plugged in.
+ */
+ public Builder setRequiresCharging(boolean requiresCharging) {
+ mRequiresCharging = requiresCharging;
+ return this;
+ }
+
+ /**
+ * Specify that to run, the task needs the device to be in idle mode. This defaults to
+ * false.
+ * <p>Idle mode is a loose definition provided by the system, which means that the device
+ * is not in use, and has not been in use for some time. As such, it is a good time to
+ * perform resource heavy tasks. Bear in mind that battery usage will still be attributed
+ * to your application, and surfaced to the user in battery stats.</p>
+ * @param requiresDeviceIdle Whether or not the device need be within an idle maintenance
+ * window.
+ */
+ public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) {
+ mRequiresDeviceIdle = requiresDeviceIdle;
+ return this;
+ }
+
+ /**
+ * Specify that this task should recur with the provided interval, not more than once per
+ * period. You have no control over when within this interval this task will be executed,
+ * only the guarantee that it will be executed at most once within this interval.
+ * A periodic task will be repeated until the phone is turned off, however it will only be
+ * persisted if the client app has declared the
+ * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission. You can schedule
+ * periodic tasks without this permission, they simply will cease to exist after the phone
+ * restarts.
+ * Setting this function on the builder with {@link #setMinimumLatency(long)} or
+ * {@link #setOverrideDeadline(long)} will result in an error.
+ * @param intervalMillis Millisecond interval for which this task will repeat.
+ */
+ public Builder setPeriodic(long intervalMillis) {
+ mIsPeriodic = true;
+ mIntervalMillis = intervalMillis;
+ return this;
+ }
+
+ /**
+ * Specify that this task should be delayed by the provided amount of time.
+ * Because it doesn't make sense setting this property on a periodic task, doing so will
+ * throw an {@link java.lang.IllegalArgumentException} when
+ * {@link android.content.Task.Builder#build()} is called.
+ * @param minLatencyMillis Milliseconds before which this task will not be considered for
+ * execution.
+ */
+ public Builder setMinimumLatency(long minLatencyMillis) {
+ mMinLatencyMillis = minLatencyMillis;
+ return this;
+ }
+
+ /**
+ * Set deadline which is the maximum scheduling latency. The task will be run by this
+ * deadline even if other requirements are not met. Because it doesn't make sense setting
+ * this property on a periodic task, doing so will throw an
+ * {@link java.lang.IllegalArgumentException} when
+ * {@link android.content.Task.Builder#build()} is called.
+ */
+ public Builder setOverrideDeadline(long maxExecutionDelayMillis) {
+ mMaxExecutionDelayMillis = maxExecutionDelayMillis;
+ return this;
+ }
+
+ /**
+ * Set up the back-off/retry policy.
+ * This defaults to some respectable values: {5 seconds, Exponential}. We cap back-off at
+ * 1hr.
+ * Note that trying to set a backoff criteria for a task with
+ * {@link #setRequiresDeviceIdle(boolean)} will throw an exception when you call build().
+ * This is because back-off typically does not make sense for these types of tasks. See
+ * {@link android.app.task.TaskService#taskFinished(android.app.task.TaskParams, boolean)}
+ * for more description of the return value for the case of a task executing while in idle
+ * mode.
+ * @param initialBackoffMillis Millisecond time interval to wait initially when task has
+ * failed.
+ * @param backoffPolicy is one of {@link BackoffPolicy}
+ */
+ public Builder setBackoffCriteria(long initialBackoffMillis, int backoffPolicy) {
+ mBackoffPolicySet = true;
+ mInitialBackoffMillis = initialBackoffMillis;
+ mBackoffPolicy = backoffPolicy;
+ return this;
+ }
+
+ /**
+ * @return The task object to hand to the TaskManager. This object is immutable.
+ */
+ public Task build() {
+ // Check that extras bundle only contains primitive types.
+ try {
+ for (String key : extras.keySet()) {
+ Object value = extras.get(key);
+ if (value == null) continue;
+ if (value instanceof Long) continue;
+ if (value instanceof Integer) continue;
+ if (value instanceof Boolean) continue;
+ if (value instanceof Float) continue;
+ if (value instanceof Double) continue;
+ if (value instanceof String) continue;
+ throw new IllegalArgumentException("Unexpected value type: "
+ + value.getClass().getName());
+ }
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (RuntimeException exc) {
+ throw new IllegalArgumentException("error unparcelling Bundle", exc);
+ }
+ // Check that a deadline was not set on a periodic task.
+ if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) {
+ throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
+ "periodic task.");
+ }
+ if (mIsPeriodic && (mMinLatencyMillis != 0L)) {
+ throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
+ "periodic task");
+ }
+ if (mBackoffPolicySet && mRequiresDeviceIdle) {
+ throw new IllegalArgumentException("An idle mode task will not respect any" +
+ " back-off policy, so calling setBackoffCriteria with" +
+ " setRequiresDeviceIdle is an error.");
+ }
+ return new Task(this);
+ }
+ }
+
+}
diff --git a/core/java/android/content/TaskManager.java b/core/java/android/content/TaskManager.java
new file mode 100644
index 0000000..d28d78a
--- /dev/null
+++ b/core/java/android/content/TaskManager.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import java.util.List;
+
+/**
+ * Class for scheduling various types of tasks with the scheduling framework on the device.
+ *
+ * Get an instance of this class through {@link Context#getSystemService(String)}.
+ */
+public abstract class TaskManager {
+ /*
+ * Returned from {@link #schedule(Task)} when an invalid parameter was supplied. This can occur
+ * if the run-time for your task is too short, or perhaps the system can't resolve the
+ * requisite {@link TaskService} in your package.
+ */
+ static final int RESULT_INVALID_PARAMETERS = -1;
+ /**
+ * Returned from {@link #schedule(Task)} if this application has made too many requests for
+ * work over too short a time.
+ */
+ // TODO: Determine if this is necessary.
+ static final int RESULT_OVER_QUOTA = -2;
+
+ /*
+ * @param task The task you wish scheduled. See {@link Task#TaskBuilder} for more detail on
+ * the sorts of tasks you can schedule.
+ * @return If >0, this int corresponds to the taskId of the successfully scheduled task.
+ * Otherwise you have to compare the return value to the error codes defined in this class.
+ */
+ public abstract int schedule(Task task);
+
+ /**
+ * Cancel a task that is pending in the TaskManager.
+ * @param taskId unique identifier for this task. Obtain this value from the tasks returned by
+ * {@link #getAllPendingTasks()}.
+ * @return
+ */
+ public abstract void cancel(int taskId);
+
+ /**
+ * Cancel all tasks that have been registered with the TaskManager by this package.
+ */
+ public abstract void cancelAll();
+
+ /**
+ * @return a list of all the tasks registered by this package that have not yet been executed.
+ */
+ public abstract List<Task> getAllPendingTasks();
+
+}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 941b726..c53e545 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
* android.R.attr#primaryUserOnly attribute.
@@ -219,6 +230,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.
@@ -330,6 +363,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..0acf043
--- /dev/null
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -0,0 +1,40 @@
+/**
+ * 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);
+ boolean isPackageEnabled(String packageName, in UserHandle user);
+ boolean isActivityEnabled(in ComponentName component, 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..cf9a296 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;
@@ -73,6 +74,9 @@ interface IPackageManager {
ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId);
+ boolean activitySupportsIntent(in ComponentName className, in Intent intent,
+ String resolvedType);
+
ActivityInfo getReceiverInfo(in ComponentName className, int flags, int userId);
ServiceInfo getServiceInfo(in ComponentName className, int flags, int userId);
@@ -107,6 +111,8 @@ interface IPackageManager {
ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags, int userId);
+ boolean canForwardTo(in Intent intent, String resolvedType, int userIdFrom, int userIdDest);
+
List<ResolveInfo> queryIntentActivities(in Intent intent,
String resolvedType, int flags, int userId);
@@ -237,6 +243,14 @@ 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);
+
+ void addForwardingIntentFilter(in IntentFilter filter, int userIdOrig, int userIdDest);
+
+ void clearForwardingIntentFilters(int userIdOrig);
+
/**
* Report the set of 'Home' activity candidates, plus (if any) which of them
* is the current "always use this one" setting.
@@ -402,6 +416,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..9087338
--- /dev/null
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -0,0 +1,167 @@
+/*
+ * 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.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+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 static final String TAG = "LauncherActivityInfo";
+
+ 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 name for the acitivty from android:name in the manifest.
+ * @return the name from android:name for the acitivity.
+ */
+ public String getName() {
+ return mActivityInfo.name;
+ }
+
+ /**
+ * 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) {
+ int iconRes = mActivityInfo.getIconResource();
+ Resources resources = null;
+ Drawable originalIcon = null;
+ try {
+ resources = mPm.getResourcesForApplication(mActivityInfo.applicationInfo);
+ try {
+ if (density != 0) {
+ originalIcon = resources.getDrawableForDensity(iconRes, density);
+ }
+ } catch (Resources.NotFoundException e) {
+ }
+ } catch (NameNotFoundException nnfe) {
+ }
+
+ if (originalIcon == null) {
+ originalIcon = mActivityInfo.loadIcon(mPm);
+ }
+
+ if (originalIcon instanceof BitmapDrawable) {
+ return mUm.getBadgedDrawableForUser(
+ originalIcon, mUser);
+ } else {
+ Log.e(TAG, "Unable to create badged icon for " + mActivityInfo);
+ }
+ 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..8025b60
--- /dev/null
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -0,0 +1,319 @@
+/*
+ * 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!
+ }
+ }
+
+ /**
+ * Checks if the package is installed and enabled for a profile.
+ *
+ * @param packageName The package to check.
+ * @param user The UserHandle of the profile.
+ *
+ * @return true if the package exists and is enabled.
+ */
+ public boolean isPackageEnabledForProfile(String packageName, UserHandle user) {
+ try {
+ return mService.isPackageEnabled(packageName, user);
+ } catch (RemoteException re) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the activity exists and it enabled for a profile.
+ *
+ * @param component The activity to check.
+ * @param user The UserHandle of the profile.
+ *
+ * @return true if the activity exists and is enabled.
+ */
+ public boolean isActivityEnabledForProfile(ComponentName component, UserHandle user) {
+ try {
+ return mService.isActivityEnabled(component, user);
+ } catch (RemoteException re) {
+ return false;
+ }
+ }
+
+
+ /**
+ * 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..484a2a1 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
@@ -1212,7 +1235,6 @@ public abstract class PackageManager {
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_LIVE_WALLPAPER = "android.software.live_wallpaper";
-
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports app widgets.
@@ -1221,6 +1243,17 @@ public abstract class PackageManager {
public static final String FEATURE_APP_WIDGETS = "android.software.app_widgets";
/**
+ * @hide
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports
+ * {@link android.service.voice.VoiceInteractionService} and
+ * {@link android.app.VoiceInteractor}.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers";
+
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports a home screen that is replaceable
* by third party applications.
@@ -1314,6 +1347,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 +1443,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 +2839,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 +2862,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 +2877,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 +2897,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 +2910,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 66d4f50..080b37b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -50,6 +50,7 @@ import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
@@ -57,7 +58,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;
@@ -148,8 +148,7 @@ public class PackageParser {
private String[] mSeparateProcesses;
private boolean mOnlyCoreApps;
private static final int SDK_VERSION = Build.VERSION.SDK_INT;
- private static final String SDK_CODENAME = "REL".equals(Build.VERSION.CODENAME)
- ? null : Build.VERSION.CODENAME;
+ private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
private int mParseError = PackageManager.INSTALL_SUCCEEDED;
@@ -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;
@@ -459,7 +459,7 @@ public class PackageParser {
return pi;
}
- private Certificate[] loadCertificates(StrictJarFile jarFile, ZipEntry je,
+ private Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry je,
byte[] readBuffer) {
try {
// We must read the stream for the JarEntry to retrieve
@@ -469,7 +469,7 @@ public class PackageParser {
// not using
}
is.close();
- return je != null ? jarFile.getCertificates(je) : null;
+ return je != null ? jarFile.getCertificateChains(je) : null;
} catch (IOException e) {
Slog.w(TAG, "Exception reading " + je.getName() + " in " + jarFile, e);
} catch (RuntimeException e) {
@@ -632,7 +632,7 @@ public class PackageParser {
try {
StrictJarFile jarFile = new StrictJarFile(mArchiveSourcePath);
- Certificate[] certs = null;
+ Certificate[][] certs = null;
if ((flags&PARSE_IS_SYSTEM) != 0) {
// If this package comes from the system image, then we
@@ -656,8 +656,8 @@ public class PackageParser {
final int N = certs.length;
for (int i=0; i<N; i++) {
Slog.i(TAG, " Public key: "
- + certs[i].getPublicKey().getEncoded()
- + " " + certs[i].getPublicKey());
+ + certs[i][0].getPublicKey().getEncoded()
+ + " " + certs[i][0].getPublicKey());
}
}
}
@@ -677,7 +677,7 @@ public class PackageParser {
ManifestDigest.fromInputStream(jarFile.getInputStream(je));
}
- final Certificate[] localCerts = loadCertificates(jarFile, je, readBuffer);
+ final Certificate[][] localCerts = loadCertificates(jarFile, je, readBuffer);
if (DEBUG_JAR) {
Slog.i(TAG, "File " + mArchiveSourcePath + " entry " + je.getName()
+ ": certs=" + certs + " ("
@@ -726,8 +726,7 @@ public class PackageParser {
final int N = certs.length;
pkg.mSignatures = new Signature[certs.length];
for (int i=0; i<N; i++) {
- pkg.mSignatures[i] = new Signature(
- certs[i].getEncoded());
+ pkg.mSignatures[i] = new Signature(certs[i]);
}
} else {
Slog.e(TAG, "Package " + pkg.packageName
@@ -739,7 +738,7 @@ public class PackageParser {
// Add the signing KeySet to the system
pkg.mSigningKeys = new HashSet<PublicKey>();
for (int i=0; i < certs.length; i++) {
- pkg.mSigningKeys.add(certs[i].getPublicKey());
+ pkg.mSigningKeys.add(certs[i][0].getPublicKey());
}
} catch (CertificateEncodingException e) {
@@ -988,7 +987,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 +1008,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 +1104,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,
@@ -1200,10 +1199,18 @@ public class PackageParser {
sa.recycle();
if (minCode != null) {
- if (!minCode.equals(SDK_CODENAME)) {
- if (SDK_CODENAME != null) {
+ boolean allowedCodename = false;
+ for (String codename : SDK_CODENAMES) {
+ if (minCode.equals(codename)) {
+ allowedCodename = true;
+ break;
+ }
+ }
+ if (!allowedCodename) {
+ if (SDK_CODENAMES.length > 0) {
outError[0] = "Requires development platform " + minCode
- + " (current platform is " + SDK_CODENAME + ")";
+ + " (current platform is any of "
+ + Arrays.toString(SDK_CODENAMES) + ")";
} else {
outError[0] = "Requires development platform " + minCode
+ " but this is a release platform.";
@@ -1219,10 +1226,18 @@ public class PackageParser {
}
if (targetCode != null) {
- if (!targetCode.equals(SDK_CODENAME)) {
- if (SDK_CODENAME != null) {
+ boolean allowedCodename = false;
+ for (String codename : SDK_CODENAMES) {
+ if (targetCode.equals(codename)) {
+ allowedCodename = true;
+ break;
+ }
+ }
+ if (!allowedCodename) {
+ if (SDK_CODENAMES.length > 0) {
outError[0] = "Requires development platform " + targetCode
- + " (current platform is " + SDK_CODENAME + ")";
+ + " (current platform is any of "
+ + Arrays.toString(SDK_CODENAMES) + ")";
} else {
outError[0] = "Requires development platform " + targetCode
+ " but this is a release platform.";
@@ -1986,6 +2001,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);
@@ -2446,6 +2463,11 @@ public class PackageParser {
}
if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_persistable, false)) {
+ a.info.flags |= ActivityInfo.FLAG_PERSISTABLE;
+ }
+
+ if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestActivity_allowEmbedded,
false)) {
a.info.flags |= ActivityInfo.FLAG_ALLOW_EMBEDDED;
@@ -3291,19 +3313,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);
@@ -3597,6 +3623,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/Signature.java b/core/java/android/content/pm/Signature.java
index 752bf8b..f4e7dc3 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -25,6 +25,7 @@ import java.io.ByteArrayInputStream;
import java.lang.ref.SoftReference;
import java.security.PublicKey;
import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
@@ -38,12 +39,28 @@ public class Signature implements Parcelable {
private int mHashCode;
private boolean mHaveHashCode;
private SoftReference<String> mStringRef;
+ private Certificate[] mCertificateChain;
/**
* Create Signature from an existing raw byte array.
*/
public Signature(byte[] signature) {
mSignature = signature.clone();
+ mCertificateChain = null;
+ }
+
+ /**
+ * Create signature from a certificate chain. Used for backward
+ * compatibility.
+ *
+ * @throws CertificateEncodingException
+ * @hide
+ */
+ public Signature(Certificate[] certificateChain) throws CertificateEncodingException {
+ mSignature = certificateChain[0].getEncoded();
+ if (certificateChain.length > 1) {
+ mCertificateChain = Arrays.copyOfRange(certificateChain, 1, certificateChain.length);
+ }
}
private static final int parseHexDigit(int nibble) {
@@ -156,6 +173,29 @@ public class Signature implements Parcelable {
return cert.getPublicKey();
}
+ /**
+ * Used for compatibility code that needs to check the certificate chain
+ * during upgrades.
+ *
+ * @throws CertificateEncodingException
+ * @hide
+ */
+ public Signature[] getChainSignatures() throws CertificateEncodingException {
+ if (mCertificateChain == null) {
+ return new Signature[] { this };
+ }
+
+ Signature[] chain = new Signature[1 + mCertificateChain.length];
+ chain[0] = this;
+
+ int i = 1;
+ for (Certificate c : mCertificateChain) {
+ chain[i++] = new Signature(c.getEncoded());
+ }
+
+ return chain;
+ }
+
@Override
public boolean equals(Object obj) {
try {
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 4c87830..c0383a3 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;
/**
@@ -26,8 +27,8 @@ import android.os.UserHandle;
*/
public class UserInfo implements Parcelable {
- /** 6 bits for user type */
- public static final int FLAG_MASK_USER_TYPE = 0x0000003F;
+ /** 8 bits for user type */
+ public static final int FLAG_MASK_USER_TYPE = 0x000000FF;
/**
* *************************** NOTE ***************************
@@ -63,6 +64,20 @@ 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;
+
+ /**
+ * Indicates that this user is disabled.
+ */
+ public static final int FLAG_DISABLED = 0x00000040;
+
+
+ public static final int NO_PROFILE_GROUP_ID = -1;
+
public int id;
public int serialNumber;
public String name;
@@ -70,6 +85,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 +99,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 +118,22 @@ public class UserInfo implements Parcelable {
return (flags & FLAG_RESTRICTED) == FLAG_RESTRICTED;
}
+ public boolean isManagedProfile() {
+ return (flags & FLAG_MANAGED_PROFILE) == FLAG_MANAGED_PROFILE;
+ }
+
+ public boolean isEnabled() {
+ return (flags & FLAG_DISABLED) != FLAG_DISABLED;
+ }
+
+ /**
+ * @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 +146,7 @@ public class UserInfo implements Parcelable {
creationTime = orig.creationTime;
lastLoggedInTime = orig.lastLoggedInTime;
partial = orig.partial;
+ profileGroupId = orig.profileGroupId;
}
public UserHandle getUserHandle() {
@@ -137,6 +171,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 +193,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 7318652..1331777 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,86 +72,112 @@ 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 */
- public static int selectDefaultTheme(int curTheme, int targetSdkVersion) {
+ /**
+ * Returns the most appropriate default theme for the specified target SDK version.
+ *
+ * @param curTheme The current theme, or 0 if not specified.
+ * @param targetSdkVersion The target SDK version.
+ * @return A theme resource identifier
+ * @hide
+ */
+ public 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.array.system_theme_sdks,
+ com.android.internal.R.array.system_theme_styles);
}
-
- /** @hide */
- public static int selectSystemTheme(int curTheme, int targetSdkVersion,
- int orig, int holo, int deviceDefault) {
+
+ /**
+ * Returns the most appropriate default theme for the specified target SDK version.
+ *
+ * @param curTheme The current theme, or 0 if not specified.
+ * @param targetSdkVersion The target SDK version.
+ * @param sdkArrayId Identifier for integer array resource containing
+ * sorted minimum SDK versions. First entry must be 0.
+ * @param themeArrayId Identifier for array resource containing the
+ * default themes that map to SDK versions.
+ * @return A theme resource identifier
+ * @hide
+ */
+ public int selectSystemTheme(
+ int curTheme, int targetSdkVersion, int sdkArrayId, int themeArrayId) {
if (curTheme != 0) {
return curTheme;
}
- if (targetSdkVersion < Build.VERSION_CODES.HONEYCOMB) {
- return orig;
- }
- if (targetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- return holo;
+
+ final int[] targetSdks = getIntArray(sdkArrayId);
+ final TypedArray defaultThemes = obtainTypedArray(themeArrayId);
+ for (int i = targetSdks.length - 1; i > 0; i--) {
+ if (targetSdkVersion >= targetSdks[i]) {
+ return defaultThemes.getResourceId(i, 0);
+ }
}
- return deviceDefault;
+
+ return defaultThemes.getResourceId(0, 0);
}
-
+
/**
* This exception is thrown by the resource APIs when a requested resource
* can not be found.
@@ -513,7 +539,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 +707,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 +738,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 +756,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 +812,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 +1271,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 +1288,8 @@ public class Resources {
*/
public void setTo(Theme other) {
AssetManager.copyTheme(mTheme, other.mTheme);
+
+ mThemeResId = other.mThemeResId;
}
/**
@@ -1246,11 +1312,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 +1339,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 +1369,7 @@ public class Resources {
}
System.out.println(s);
}
+ AssetManager.applyStyle(mTheme, 0, resid, 0, attrs, array.mData, array.mIndices);
return array;
}
@@ -1361,20 +1423,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 +1470,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 +1525,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 +1537,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 +1570,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 +1582,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*/ long getNativeTheme() {
return mTheme;
}
+
+ /*package*/ int getAppliedStyleResId() {
+ return mThemeResId;
+ }
}
/**
@@ -1492,7 +1623,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 +1633,6 @@ public class Resources {
mAssets.retrieveAttributes(parser.mParseState, attrs,
array.mData, array.mIndices);
- array.mRsrcs = attrs;
array.mXml = parser;
return array;
@@ -1574,7 +1704,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 +1737,8 @@ public class Resources {
+ " final compat is " + mCompatibilityInfo);
}
- clearDrawableCacheLocked(mDrawableCache, configChanges);
- clearDrawableCacheLocked(mColorDrawableCache, configChanges);
+ clearDrawableCachesLocked(mDrawableCache, configChanges);
+ clearDrawableCachesLocked(mColorDrawableCache, configChanges);
mColorStateListCache.clear();
@@ -1621,18 +1751,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 +1792,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 +2166,7 @@ public class Resources {
/**
* @hide
*/
- public LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() {
+ public LongSparseArray<ConstantState> getPreloadedDrawables() {
return sPreloadedDrawables[0];
}
@@ -2006,6 +2184,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 +2205,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);
+ }
+
+ return dr;
+ }
- String file = value.string.toString();
+ 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 \"" + getResourceName(id) + "\" ("
+ + Integer.toHexString(id) + ") 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 +2449,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 +2577,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 +2596,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..722d956 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<Integer> CONTROL_MAX_REGIONS =
- new Key<Integer>("android.control.maxRegions", int.class);
+ public static final Key<byte[]> EDGE_AVAILABLE_EDGE_MODES =
+ new Key<byte[]>("android.edge.availableEdgeModes", byte[].class);
/**
- * <p>
- * Whether this camera has a
- * flash
- * </p>
- * <p>
- * If no flash, none of the flash controls do
- * anything. All other metadata should return 0
- * </p>
+ * <p>Whether this camera device has a
+ * flash.</p>
+ * <p>If no flash, none of the flash controls do
+ * anything. All other metadata should return 0.</p>
*/
- public static final Key<Byte> FLASH_INFO_AVAILABLE =
- new Key<Byte>("android.flash.info.available", byte.class);
+ public static final Key<Boolean> FLASH_INFO_AVAILABLE =
+ new Key<Boolean>("android.flash.info.available", boolean.class);
/**
- * <p>
- * Supported resolutions for the JPEG
- * thumbnail
- * </p>
+ * <p>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[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES =
+ new Key<byte[]>("android.hotPixel.availableHotPixelModes", byte[].class);
+
+ /**
+ * <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,1011 @@ 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 (but not stalling), and processed (and stalling)
+ * formats respectively. For example, assuming that JPEG is typically a processed and
+ * stalling stream, 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_STREAM_CONFIGURATIONS android.scaler.availableStreamConfigurations}.
+ * The formats defined in {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS android.scaler.availableStreamConfigurations} can be catergorized
+ * into the 3 stream types as below:</p>
+ * <ul>
+ * <li>Processed (but stalling): any non-RAW format with a stallDurations &gt; 0.
+ * Typically JPEG format (ImageFormat#JPEG).</li>
+ * <li>Raw formats: ImageFormat#RAW_SENSOR and ImageFormat#RAW_OPAQUE.</li>
+ * <li>Processed (but not-stalling): any non-RAW format without a stall duration.
+ * Typically ImageFormat#YUV_420_888, ImageFormat#NV21, ImageFormat#YV12.</li>
+ * </ul>
+ *
+ * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS
*/
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>
+ *
+ * @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>The standard reference illuminant used as the scene light source when
+ * calculating the {@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM1 android.sensor.colorTransform1},
+ * {@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1 android.sensor.calibrationTransform1}, and
+ * {@link CameraCharacteristics#SENSOR_FORWARD_MATRIX1 android.sensor.forwardMatrix1} matrices.</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 calibrating camera devices.</p>
+ * <p>If this tag is present, then {@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM1 android.sensor.colorTransform1},
+ * {@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1 android.sensor.calibrationTransform1}, and
+ * {@link CameraCharacteristics#SENSOR_FORWARD_MATRIX1 android.sensor.forwardMatrix1} will also be present.</p>
+ * <p>Some devices may choose to provide a second set of calibration
+ * information for improved quality, including
+ * {@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 android.sensor.referenceIlluminant2} and its corresponding matrices.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1
+ * @see CameraCharacteristics#SENSOR_COLOR_TRANSFORM1
+ * @see CameraCharacteristics#SENSOR_FORWARD_MATRIX1
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_FLUORESCENT
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_TUNGSTEN
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_FLASH
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_FINE_WEATHER
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_CLOUDY_WEATHER
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_SHADE
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT_FLUORESCENT
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_DAY_WHITE_FLUORESCENT
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_COOL_WHITE_FLUORESCENT
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_WHITE_FLUORESCENT
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_STANDARD_A
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_STANDARD_B
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_STANDARD_C
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_D55
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_D65
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_D75
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_D50
+ * @see #SENSOR_REFERENCE_ILLUMINANT1_ISO_STUDIO_TUNGSTEN
+ */
+ public static final Key<Integer> SENSOR_REFERENCE_ILLUMINANT1 =
+ new Key<Integer>("android.sensor.referenceIlluminant1", int.class);
+
+ /**
+ * <p>The standard reference illuminant used as the scene light source when
+ * calculating the {@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM2 android.sensor.colorTransform2},
+ * {@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM2 android.sensor.calibrationTransform2}, and
+ * {@link CameraCharacteristics#SENSOR_FORWARD_MATRIX2 android.sensor.forwardMatrix2} matrices.</p>
+ * <p>See {@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 android.sensor.referenceIlluminant1} for more details.
+ * Valid values for this are the same as those given for the first
+ * reference illuminant.</p>
+ * <p>If this tag is present, then {@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM2 android.sensor.colorTransform2},
+ * {@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM2 android.sensor.calibrationTransform2}, and
+ * {@link CameraCharacteristics#SENSOR_FORWARD_MATRIX2 android.sensor.forwardMatrix2} will also be present.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM2
+ * @see CameraCharacteristics#SENSOR_COLOR_TRANSFORM2
+ * @see CameraCharacteristics#SENSOR_FORWARD_MATRIX2
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final Key<Byte> SENSOR_REFERENCE_ILLUMINANT2 =
+ new Key<Byte>("android.sensor.referenceIlluminant2", byte.class);
+
+ /**
+ * <p>A per-device calibration transform matrix that maps from the
+ * reference sensor colorspace to the actual device sensor colorspace.</p>
+ * <p>This matrix is used to correct for per-device variations in the
+ * sensor colorspace, and is used for processing raw buffer data.</p>
+ * <p>The matrix is expressed as a 3x3 matrix in row-major-order, and
+ * contains a per-device calibration transform that maps colors
+ * from reference sensor color space (i.e. the "golden module"
+ * colorspace) into this camera device's native sensor color
+ * space under the first reference illuminant
+ * ({@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 android.sensor.referenceIlluminant1}).</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final Key<Rational[]> SENSOR_CALIBRATION_TRANSFORM1 =
+ new Key<Rational[]>("android.sensor.calibrationTransform1", Rational[].class);
+
+ /**
+ * <p>A per-device calibration transform matrix that maps from the
+ * reference sensor colorspace to the actual device sensor colorspace
+ * (this is the colorspace of the raw buffer data).</p>
+ * <p>This matrix is used to correct for per-device variations in the
+ * sensor colorspace, and is used for processing raw buffer data.</p>
+ * <p>The matrix is expressed as a 3x3 matrix in row-major-order, and
+ * contains a per-device calibration transform that maps colors
+ * from reference sensor color space (i.e. the "golden module"
+ * colorspace) into this camera device's native sensor color
+ * space under the second reference illuminant
+ * ({@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 android.sensor.referenceIlluminant2}).</p>
+ * <p>This matrix will only be present if the second reference
+ * illuminant is present.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2
+ */
+ public static final Key<Rational[]> SENSOR_CALIBRATION_TRANSFORM2 =
+ new Key<Rational[]>("android.sensor.calibrationTransform2", Rational[].class);
+
+ /**
+ * <p>A matrix that transforms color values from CIE XYZ color space to
+ * reference sensor color space.</p>
+ * <p>This matrix is used to convert from the standard CIE XYZ color
+ * space to the reference sensor colorspace, and is used when processing
+ * raw buffer data.</p>
+ * <p>The 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 sensor color space (i.e. the
+ * "golden module" colorspace) under the first reference illuminant
+ * ({@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 android.sensor.referenceIlluminant1}).</p>
+ * <p>The white points chosen in both the reference sensor color space
+ * and the CIE XYZ colorspace when calculating this transform will
+ * match the standard white point for the first reference illuminant
+ * (i.e. no chromatic adaptation will be applied by this transform).</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final Key<Rational[]> SENSOR_COLOR_TRANSFORM1 =
+ new Key<Rational[]>("android.sensor.colorTransform1", Rational[].class);
+
+ /**
+ * <p>A matrix that transforms color values from CIE XYZ color space to
+ * reference sensor color space.</p>
+ * <p>This matrix is used to convert from the standard CIE XYZ color
+ * space to the reference sensor colorspace, and is used when processing
+ * raw buffer data.</p>
+ * <p>The 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 sensor color space (i.e. the
+ * "golden module" colorspace) under the second reference illuminant
+ * ({@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 android.sensor.referenceIlluminant2}).</p>
+ * <p>The white points chosen in both the reference sensor color space
+ * and the CIE XYZ colorspace when calculating this transform will
+ * match the standard white point for the second reference illuminant
+ * (i.e. no chromatic adaptation will be applied by this transform).</p>
+ * <p>This matrix will only be present if the second reference
+ * illuminant is present.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2
+ */
+ public static final Key<Rational[]> SENSOR_COLOR_TRANSFORM2 =
+ new Key<Rational[]>("android.sensor.colorTransform2", Rational[].class);
+
+ /**
+ * <p>A matrix that transforms white balanced camera colors from the reference
+ * sensor colorspace to the CIE XYZ colorspace with a D50 whitepoint.</p>
+ * <p>This matrix is used to convert to the standard CIE XYZ colorspace, and
+ * is used when processing raw buffer data.</p>
+ * <p>This matrix is expressed as a 3x3 matrix in row-major-order, and contains
+ * a color transform matrix that maps white balanced colors from the
+ * reference sensor color space to the CIE XYZ color space with a D50 white
+ * point.</p>
+ * <p>Under the first reference illuminant ({@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 android.sensor.referenceIlluminant1})
+ * this matrix is chosen so that the standard white point for this reference
+ * illuminant in the reference sensor colorspace is mapped to D50 in the
+ * CIE XYZ colorspace.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final Key<Rational[]> SENSOR_FORWARD_MATRIX1 =
+ new Key<Rational[]>("android.sensor.forwardMatrix1", Rational[].class);
+
+ /**
+ * <p>A matrix that transforms white balanced camera colors from the reference
+ * sensor colorspace to the CIE XYZ colorspace with a D50 whitepoint.</p>
+ * <p>This matrix is used to convert to the standard CIE XYZ colorspace, and
+ * is used when processing raw buffer data.</p>
+ * <p>This matrix is expressed as a 3x3 matrix in row-major-order, and contains
+ * a color transform matrix that maps white balanced colors from the
+ * reference sensor color space to the CIE XYZ color space with a D50 white
+ * point.</p>
+ * <p>Under the second reference illuminant ({@link CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 android.sensor.referenceIlluminant2})
+ * this matrix is chosen so that the standard white point for this reference
+ * illuminant in the reference sensor colorspace is mapped to D50 in the
+ * CIE XYZ colorspace.</p>
+ * <p>This matrix will only be present if the second reference
+ * illuminant is present.</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_REFERENCE_ILLUMINANT2
+ */
+ public static final Key<Rational[]> SENSOR_FORWARD_MATRIX2 =
+ new Key<Rational[]>("android.sensor.forwardMatrix2", Rational[].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..bb290af 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.
*
@@ -597,6 +570,14 @@ public interface CameraDevice extends AutoCloseable {
public static abstract class CaptureListener {
/**
+ * This constant is used to indicate that no images were captured for
+ * the request.
+ *
+ * @hide
+ */
+ public static final int NO_FRAMES_CAPTURED = -1;
+
+ /**
* This method is called when the camera device has started capturing
* the output image for the request, at the beginning of image exposure.
*
@@ -720,9 +701,12 @@ public interface CameraDevice extends AutoCloseable {
* The CameraDevice sending the callback.
* @param sequenceId
* A sequence ID returned by the {@link #capture} family of functions.
- * @param frameNumber
+ * @param lastFrameNumber
* The last frame number (returned by {@link CaptureResult#getFrameNumber}
* or {@link CaptureFailure#getFrameNumber}) in the capture sequence.
+ * The last frame number may be equal to NO_FRAMES_CAPTURED if no images
+ * were captured for this sequence. This can happen, for example, when a
+ * repeating request or burst is cleared right after being set.
*
* @see CaptureResult#getFrameNumber()
* @see CaptureFailure#getFrameNumber()
@@ -730,7 +714,7 @@ public interface CameraDevice extends AutoCloseable {
* @see CaptureFailure#getSequenceId()
*/
public void onCaptureSequenceCompleted(CameraDevice camera,
- int sequenceId, int frameNumber) {
+ int sequenceId, int lastFrameNumber) {
// default empty implementation
}
}
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..ba8db3a 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,313 @@ 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#SENSOR_REFERENCE_ILLUMINANT1
+ //
+
+ /**
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT = 1;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_FLUORESCENT = 2;
+
+ /**
+ * <p>Incandescent light</p>
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_TUNGSTEN = 3;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_FLASH = 4;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_FINE_WEATHER = 9;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_CLOUDY_WEATHER = 10;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_SHADE = 11;
+
+ /**
+ * <p>D 5700 - 7100K</p>
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT_FLUORESCENT = 12;
+
+ /**
+ * <p>N 4600 - 5400K</p>
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_DAY_WHITE_FLUORESCENT = 13;
+
+ /**
+ * <p>W 3900 - 4500K</p>
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_COOL_WHITE_FLUORESCENT = 14;
+
+ /**
+ * <p>WW 3200 - 3700K</p>
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_WHITE_FLUORESCENT = 15;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_STANDARD_A = 17;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_STANDARD_B = 18;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_STANDARD_C = 19;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_D55 = 20;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_D65 = 21;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_D75 = 22;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_D50 = 23;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT1_ISO_STUDIO_TUNGSTEN = 24;
+
+ //
// 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 +586,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 +664,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 +698,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 +774,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 +792,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 +840,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 +863,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 +884,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 +907,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 +999,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 +1127,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 +1299,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 +1323,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 +1400,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 +1556,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 +1588,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 +1623,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 +1677,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 +1742,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,50 +1775,61 @@ 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;
@@ -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..d8981c8 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,277 @@ 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>
+ * <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>Optional</b> - This value may be null on some devices.
- *
- * <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>The estimated camera neutral color in the native sensor colorspace at
+ * the time of capture.</p>
+ * <p>This value gives the neutral color point encoded as an RGB value in the
+ * native sensor color space. The neutral color point indicates the
+ * currently estimated white point of the scene illumination. It can be
+ * used to interpolate between the provided color transforms when
+ * processing raw sensor data.</p>
+ * <p>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 +1756,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 +1909,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 +2048,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<Long> SYNC_FRAME_NUMBER =
+ new Key<Long>("android.sync.frameNumber", long.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..988f8f9 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);
@@ -135,7 +157,14 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
mCameraId = cameraId;
mDeviceListener = listener;
mDeviceHandler = handler;
- TAG = String.format("CameraDevice-%s-JV", mCameraId);
+
+ final int MAX_TAG_LEN = 23;
+ String tag = String.format("CameraDevice-JV-%s", mCameraId);
+ if (tag.length() > MAX_TAG_LEN) {
+ tag = tag.substring(0, MAX_TAG_LEN);
+ }
+ TAG = tag;
+
DEBUG = Log.isLoggable(TAG, Log.DEBUG);
}
@@ -251,22 +280,96 @@ 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);
+ }
+
+ /**
+ * This method checks lastFrameNumber returned from ICameraDeviceUser methods for
+ * starting and stopping repeating request and flushing.
+ *
+ * <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never
+ * sent to HAL. Then onCaptureSequenceCompleted is immediately triggered.
+ * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber pair
+ * is added to the list mFrameNumberRequestPairs.</p>
+ *
+ * @param requestId the request ID of the current repeating request.
+ *
+ * @param lastFrameNumber last frame number returned from binder.
+ */
+ private void checkEarlyTriggerSequenceComplete(
+ final int requestId, final long lastFrameNumber) {
+ // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request
+ // was never sent to HAL. Should trigger onCaptureSequenceCompleted immediately.
+ if (lastFrameNumber == CaptureListener.NO_FRAMES_CAPTURED) {
+ final CaptureListenerHolder holder;
+ int index = mCaptureListenerMap.indexOfKey(requestId);
+ holder = (index >= 0) ? mCaptureListenerMap.valueAt(index) : null;
+ if (holder != null) {
+ mCaptureListenerMap.removeAt(index);
+ if (DEBUG) {
+ Log.v(TAG, String.format(
+ "remove holder for requestId %d, "
+ + "because lastFrame is %d.",
+ requestId, lastFrameNumber));
+ }
+ }
+ if (holder != null) {
+ if (DEBUG) {
+ Log.v(TAG, "immediately trigger onCaptureSequenceCompleted because"
+ + " request did not reach HAL");
+ }
+
+ Runnable resultDispatch = new Runnable() {
+ @Override
+ public void run() {
+ if (!CameraDevice.this.isClosed()) {
+ if (DEBUG) {
+ Log.d(TAG, String.format(
+ "early trigger sequence complete for request %d",
+ requestId));
+ }
+ if (lastFrameNumber < Integer.MIN_VALUE
+ || lastFrameNumber > Integer.MAX_VALUE) {
+ throw new AssertionError(lastFrameNumber + " cannot be cast to int");
+ }
+ holder.getListener().onCaptureSequenceCompleted(
+ CameraDevice.this,
+ requestId,
+ (int)lastFrameNumber);
+ }
+ }
+ };
+ holder.getHandler().post(resultDispatch);
+ } else {
+ Log.w(TAG, String.format(
+ "did not register listener to request %d",
+ requestId));
+ }
+ } else {
+ mFrameNumberRequestPairs.add(
+ new SimpleEntry<Long, Integer>(lastFrameNumber,
+ requestId));
+ }
}
- 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,21 +386,39 @@ 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 (DEBUG) {
+ Log.v(TAG, "last frame number " + lastFrameNumberRef.getNumber());
+ }
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
// impossible
return -1;
}
+
if (listener != null) {
- mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener, request,
- handler, repeating));
+ mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener,
+ requestList, handler, repeating));
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Listen for request " + requestId + " is null");
+ }
}
+ long lastFrameNumber = lastFrameNumberRef.getNumber();
+
if (repeating) {
+ if (mRepeatingRequestId != REQUEST_ID_NONE) {
+ checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
+ }
mRepeatingRequestId = requestId;
+ } else {
+ mFrameNumberRequestPairs.add(
+ new SimpleEntry<Long, Integer>(lastFrameNumber, requestId));
}
if (mIdle) {
@@ -312,18 +433,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 +460,17 @@ 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();
+
+ checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber);
+
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
@@ -351,8 +481,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 +499,6 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
mRepeatingRequestId = REQUEST_ID_NONE;
- mRepeatingRequestIdDeletedList.clear();
- mCaptureListenerMap.clear();
}
}
@@ -382,7 +509,13 @@ 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();
+ checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
+ mRepeatingRequestId = REQUEST_ID_NONE;
+ }
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
@@ -428,18 +561,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 +584,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 +610,117 @@ 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) {
+ Log.e(TAG, String.format(
+ "result frame number %d comes out of order, should be %d + 1",
+ frameNumber, mCompletedFrameNumber));
+ }
+ 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 = mCaptureListenerMap.indexOfKey(requestId);
+ holder = (index >= 0) ? mCaptureListenerMap.valueAt(index)
+ : null;
+ if (holder != null) {
+ mCaptureListenerMap.removeAt(index);
+ if (DEBUG) {
+ Log.v(TAG, String.format(
+ "remove holder for requestId %d, "
+ + "because lastFrame %d is <= %d",
+ requestId, frameNumberRequestPair.getKey(),
+ completedFrameNumber));
+ }
+ }
+ }
+ 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));
+ }
+
+ long lastFrameNumber = frameNumberRequestPair.getKey();
+ if (lastFrameNumber < Integer.MIN_VALUE
+ || lastFrameNumber > Integer.MAX_VALUE) {
+ throw new AssertionError(lastFrameNumber
+ + " cannot be cast to int");
+ }
+ holder.getListener().onCaptureSequenceCompleted(
+ CameraDevice.this,
+ requestId,
+ (int)lastFrameNumber);
+ }
+ }
+ };
+ holder.getHandler().post(resultDispatch);
+ }
+
+ }
+ }
+ }
+
public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
//
@@ -495,7 +755,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 +770,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 +781,14 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
CameraDevice.this.mDeviceHandler.post(r);
}
+
+ // Fire onCaptureSequenceCompleted
+ if (DEBUG) {
+ Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber()));
+ }
+ mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true);
+ checkAndFireSequenceComplete();
+
}
@Override
@@ -538,7 +807,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 +828,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,56 +841,46 @@ 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);
+ Log.v(TAG, "Received result frame " + resultExtras.getFrameNumber() + " for id "
+ + requestId);
}
final CaptureListenerHolder holder;
-
- 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();
- }
- }
- }
+ Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
+ boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
+ // Update tracker (increment counter) when it's not a partial result.
+ if (!quirkIsPartialResult) {
+ mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/false);
}
// Check if we have a listener for this
if (holder == null) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "holder is null, early return at frame "
+ + resultExtras.getFrameNumber());
+ }
return;
}
- if (isClosed()) return;
+ if (isClosed()) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "camera is closed, early return at frame "
+ + resultExtras.getFrameNumber());
+ }
+ 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 +915,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
holder.getHandler().post(resultDispatch);
+
+ // Fire onCaptureSequenceCompleted
+ if (!quirkIsPartialResult) {
+ 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/hdmi/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java
index 8578a32..eafaed6 100644
--- a/core/java/android/hardware/hdmi/HdmiCec.java
+++ b/core/java/android/hardware/hdmi/HdmiCec.java
@@ -85,7 +85,7 @@ public final class HdmiCec {
public static final int ADDR_RESERVED_2 = 13;
/** Logical address for TV other than the one assigned with {@link #ADDR_TV} */
- public static final int ADDR_FREE_USE = 14;
+ public static final int ADDR_SPECIFIC_USE = 14;
/** Logical address for devices to which address cannot be allocated */
public static final int ADDR_UNREGISTERED = 15;
@@ -179,6 +179,7 @@ public final class HdmiCec {
DEVICE_RECORDER, // ADDR_RECORDER_3
DEVICE_TUNER, // ADDR_TUNER_4
DEVICE_PLAYBACK, // ADDR_PLAYBACK_3
+ DEVICE_TV, // ADDR_SPECIFIC_USE
};
private static final String[] DEFAULT_NAMES = {
@@ -194,6 +195,7 @@ public final class HdmiCec {
"Recorder_3",
"Tuner_4",
"Playback_3",
+ "Secondary_TV",
};
private HdmiCec() { } // Prevents instantiation.
@@ -221,9 +223,7 @@ public final class HdmiCec {
* @return true if the given address is valid
*/
public static boolean isValidAddress(int address) {
- // TODO: We leave out the address 'free use(14)' for now. Check this later
- // again to make sure it is a valid address for communication.
- return (ADDR_TV <= address && address <= ADDR_PLAYBACK_3);
+ return (ADDR_TV <= address && address <= ADDR_SPECIFIC_USE);
}
/**
diff --git a/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.java b/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.java
new file mode 100644
index 0000000..9698445
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.java
@@ -0,0 +1,168 @@
+/*
+ * 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.hdmi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class to encapsulate device information for HDMI-CEC. This container
+ * include basic information such as logical address, physical address and
+ * device type, and additional information like vendor id and osd name.
+ */
+public final class HdmiCecDeviceInfo implements Parcelable {
+ // Logical address, phsical address, device type, vendor id and display name
+ // are immutable value.
+ private final int mLogicalAddress;
+ private final int mPhysicalAddress;
+ private final int mDeviceType;
+ private final int mVendorId;
+ private final String mDisplayName;
+
+
+ /**
+ * A helper class to deserialize {@link HdmiCecDeviceInfo} for a parcel.
+ */
+ public static final Parcelable.Creator<HdmiCecDeviceInfo> CREATOR =
+ new Parcelable.Creator<HdmiCecDeviceInfo>() {
+ @Override
+ public HdmiCecDeviceInfo createFromParcel(Parcel source) {
+ int logicalAddress = source.readInt();
+ int physicalAddress = source.readInt();
+ int deviceType = source.readInt();
+ int vendorId = source.readInt();
+ String displayName = source.readString();
+ return new HdmiCecDeviceInfo(logicalAddress, physicalAddress, deviceType,
+ vendorId, displayName);
+ }
+
+ @Override
+ public HdmiCecDeviceInfo[] newArray(int size) {
+ return new HdmiCecDeviceInfo[size];
+ }
+ };
+
+ /**
+ * Constructor.
+ *
+ * @param logicalAddress logical address of HDMI-Cec device.
+ * For more details, refer {@link HdmiCec}
+ * @param physicalAddress physical address of HDMI-Cec device
+ * @param deviceType type of device. For more details, refer {@link HdmiCec}
+ * @param vendorId vendor id of device. It's used for vendor specific command
+ * @param displayName name of device
+ * @hide
+ */
+ public HdmiCecDeviceInfo(int logicalAddress, int physicalAddress, int deviceType,
+ int vendorId, String displayName) {
+ mLogicalAddress = logicalAddress;
+ mPhysicalAddress = physicalAddress;
+ mDeviceType = deviceType;
+ mDisplayName = displayName;
+ mVendorId = vendorId;
+ }
+
+ /**
+ * Return the logical address of the device. It can have 0-15 values.
+ * For more details, refer constants between {@link HdmiCec#ADDR_TV}
+ * and {@link HdmiCec#ADDR_UNREGISTERED}.
+ */
+ public int getLogicalAddress() {
+ return mLogicalAddress;
+ }
+
+ /**
+ * Return the physical address of the device.
+ */
+ public int getPhysicalAddress() {
+ return mPhysicalAddress;
+ }
+
+ /**
+ * Return type of the device. For more details, refer constants between
+ * {@link HdmiCec#DEVICE_TV} and {@link HdmiCec#DEVICE_INACTIVE}.
+ */
+ public int getDeviceType() {
+ return mDeviceType;
+ }
+
+ /**
+ * Return display (OSD) name of the device.
+ */
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ /**
+ * Return vendor id of the device. Vendor id is used to distinguish devices
+ * built by other manufactures. This is required for vendor-specific command
+ * on CEC standard.
+ */
+ public int getVendorId() {
+ return mVendorId;
+ }
+
+ /**
+ * Describe the kinds of special objects contained in this Parcelable's
+ * marshalled representation.
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Serialize this object into a {@link Parcel}.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be 0 or {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE}.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mLogicalAddress);
+ dest.writeInt(mPhysicalAddress);
+ dest.writeInt(mDeviceType);
+ dest.writeInt(mVendorId);
+ dest.writeString(mDisplayName);
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer s = new StringBuffer();
+ s.append("logical_address: ").append(mLogicalAddress).append(", ");
+ s.append("physical_address: ").append(mPhysicalAddress).append(", ");
+ s.append("device_type: ").append(mDeviceType).append(", ");
+ s.append("vendor_id: ").append(mVendorId).append(", ");
+ s.append("display_name: ").append(mDisplayName);
+ return s.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof HdmiCecDeviceInfo)) {
+ return false;
+ }
+
+ HdmiCecDeviceInfo other = (HdmiCecDeviceInfo) obj;
+ return mLogicalAddress == other.mLogicalAddress
+ && mPhysicalAddress == other.mPhysicalAddress
+ && mDeviceType == other.mDeviceType
+ && mVendorId == other.mVendorId
+ && mDisplayName.equals(other.mDisplayName);
+ }
+}
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/GeofenceHardware.java b/core/java/android/hardware/location/GeofenceHardware.java
index 21de9f5..4c074e9 100644
--- a/core/java/android/hardware/location/GeofenceHardware.java
+++ b/core/java/android/hardware/location/GeofenceHardware.java
@@ -79,7 +79,7 @@ public final class GeofenceHardware {
*/
public static final int MONITOR_UNSUPPORTED = 2;
- // The following constants need to match geofence flags in gps.h
+ // The following constants need to match geofence flags in gps.h and fused_location.h
/**
* The constant to indicate that the user has entered the geofence.
*/
@@ -92,7 +92,7 @@ public final class GeofenceHardware {
/**
* The constant to indicate that the user is uncertain with respect to a
- * geofence. nn
+ * geofence.
*/
public static final int GEOFENCE_UNCERTAIN = 1<<2L;
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..e6dbcd0 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -249,6 +249,18 @@ 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;
+
+ /**
+ * Passing this flag into a call to {@link #setCursorAnchorMonitorMode(int)} will result in
+ * the cursor rectangle being provided in screen coordinates to subsequent
+ * {@link #onUpdateCursor(Rect)} callbacks.
+ */
+ public static final int CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT = 0x1;
+
InputMethodManager mImm;
int mTheme = 0;
@@ -642,12 +654,11 @@ public class InputMethodService extends AbstractInputMethodService {
return false;
}
- @Override public void onCreate() {
- mTheme = Resources.selectSystemTheme(mTheme,
- getApplicationInfo().targetSdkVersion,
- android.R.style.Theme_InputMethod,
- android.R.style.Theme_Holo_InputMethod,
- android.R.style.Theme_DeviceDefault_InputMethod);
+ @Override
+ public void onCreate() {
+ mTheme = getResources().selectSystemTheme(mTheme, getApplicationInfo().targetSdkVersion,
+ com.android.internal.R.array.system_theme_sdks,
+ com.android.internal.R.array.system_theme_ime_styles);
super.setTheme(mTheme);
super.onCreate();
mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
@@ -1692,15 +1703,24 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
- * Called when the application has reported a new location of its text
- * cursor. This is only called if explicitly requested by the input method.
- * The default implementation does nothing.
+ * Called when the application has reported a new location of its text cursor. This is only
+ * called if explicitly requested by the input method. The default implementation does nothing.
+ * @param newCursor The new cursor position, in screen coordinates if the input method calls
+ * {@link #setCursorAnchorMonitorMode} with {@link #CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT}.
+ * Otherwise, this is in local coordinates.
*/
public void onUpdateCursor(Rect newCursor) {
// Intentionally empty
}
/**
+ * 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 +2342,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/INetworkScoreCache.aidl b/core/java/android/net/INetworkScoreCache.aidl
new file mode 100644
index 0000000..35601ce
--- /dev/null
+++ b/core/java/android/net/INetworkScoreCache.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.net;
+
+import android.net.ScoredNetwork;
+
+/**
+ * A service which stores a subset of scored networks from the active network scorer.
+ *
+ * <p>To be implemented by network subsystems (e.g. Wi-Fi). NetworkScoreService will propagate
+ * scores down to each subsystem depending on the network type. Implementations may register for
+ * a given network type by calling NetworkScoreManager.registerNetworkSubsystem.
+ *
+ * <p>A proper implementation should throw SecurityException whenever the caller is not privileged.
+ * It may request scores by calling NetworkScoreManager#requestScores(NetworkKey[]); a call to
+ * updateScores may follow but may not depending on the active scorer's implementation, and in
+ * general this method may be called at any time.
+ *
+ * <p>Implementations should also override dump() so that "adb shell dumpsys network_score" includes
+ * the current scores for each network for debugging purposes.
+ * @hide
+ */
+interface INetworkScoreCache
+{
+ void updateScores(in List<ScoredNetwork> networks);
+
+ void clearScores();
+}
+
diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl
new file mode 100644
index 0000000..626bd2a
--- /dev/null
+++ b/core/java/android/net/INetworkScoreService.aidl
@@ -0,0 +1,61 @@
+/**
+ * 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.net.INetworkScoreCache;
+import android.net.ScoredNetwork;
+
+/**
+ * A service for updating network scores from a network scorer application.
+ * @hide
+ */
+interface INetworkScoreService
+{
+ /**
+ * Update scores.
+ * @return whether the update was successful.
+ * @throws SecurityException if the caller is not the current active scorer.
+ */
+ boolean updateScores(in ScoredNetwork[] networks);
+
+ /**
+ * Clear all scores.
+ * @return whether the clear was successful.
+ * @throws SecurityException if the caller is neither the current active scorer nor the system.
+ */
+ boolean clearScores();
+
+ /**
+ * Set the active scorer and clear existing scores.
+ * @param packageName the package name of the new scorer to use.
+ * @return true if the operation succeeded, or false if the new package is not a valid scorer.
+ * @throws SecurityException if the caller is not the system.
+ */
+ boolean setActiveScorer(in String packageName);
+
+ /**
+ * Register a network subsystem for scoring.
+ *
+ * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
+ * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores.
+ * @throws SecurityException if the caller is not the system.
+ * @throws IllegalArgumentException if a score cache is already registed for this type.
+ * @hide
+ */
+ void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache);
+
+}
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..30b61c5 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -54,7 +54,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class MobileDataStateTracker extends BaseNetworkStateTracker {
private static final String TAG = "MobileDataStateTracker";
- private static final boolean DBG = true;
+ private static final boolean DBG = false;
private static final boolean VDBG = false;
private PhoneConstants.DataState mMobileDataState;
@@ -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..bc19658
--- /dev/null
+++ b/core/java/android/net/NetworkKey.java
@@ -0,0 +1,128 @@
+/*
+ * 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.Objects;
+
+/**
+ * Information which identifies a specific network.
+ *
+ * @hide
+ */
+// NOTE: Ideally, we would abstract away the details of what identifies a network of a specific
+// type, so that all networks appear the same and can be scored without concern to the network type
+// itself. However, because no such cross-type identifier currently exists in the Android framework,
+// and because systems might obtain information about networks from sources other than Android
+// devices, we need to provide identifying details about each specific network type (wifi, cell,
+// etc.) so that clients can pull out these details depending on the type of network.
+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 boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ NetworkKey that = (NetworkKey) o;
+
+ return type == that.type && Objects.equals(wifiKey, that.wifiKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, wifiKey);
+ }
+
+ @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..352512e
--- /dev/null
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -0,0 +1,206 @@
+/*
+ * 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;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+/**
+ * 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 an active 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 active scorer with
+ * {@link #getActiveScorerPackage()} and request to change the active scorer by sending an
+ * {@link #ACTION_CHANGE_ACTIVE} broadcast with another scorer.
+ *
+ * @hide
+ */
+public class NetworkScoreManager {
+ /**
+ * Activity action: ask the user to change the active network scorer. This will show a dialog
+ * that asks the user whether they want to replace the current active scorer with the one
+ * specified in {@link #EXTRA_PACKAGE_NAME}. The activity will finish with RESULT_OK if the
+ * active scorer was changed or RESULT_CANCELED if it failed for any reason.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHANGE_ACTIVE = "android.net.scoring.CHANGE_ACTIVE";
+
+ /**
+ * Extra used with {@link #ACTION_CHANGE_ACTIVE} 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 active 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;
+ private final INetworkScoreService mService;
+
+ /** @hide */
+ public NetworkScoreManager(Context context) {
+ mContext = context;
+ IBinder iBinder = ServiceManager.getService(Context.NETWORK_SCORE_SERVICE);
+ mService = INetworkScoreService.Stub.asInterface(iBinder);
+ }
+
+ /**
+ * Obtain the package name of the current active network scorer.
+ *
+ * <p>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_ACTIVE} intent.
+ * @return the full package name of the current active scorer, or null if there is no active
+ * scorer.
+ */
+ public String getActiveScorerPackage() {
+ return NetworkScorerAppManager.getActiveScorer(mContext);
+ }
+
+ /**
+ * Update network scores.
+ *
+ * <p>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.
+ * @return whether the update was successful.
+ * @throws SecurityException if the caller is not the active scorer.
+ */
+ public boolean updateScores(ScoredNetwork[] networks) throws SecurityException {
+ try {
+ return mService.updateScores(networks);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Clear network scores.
+ *
+ * <p>Should be called when all scores need to be invalidated, i.e. because the scoring
+ * algorithm has changed and old scores can no longer be compared to future scores.
+ *
+ * <p>Note that scores will be cleared automatically when the active scorer changes, as scores
+ * from one scorer cannot be compared to those from another scorer.
+ *
+ * @return whether the clear was successful.
+ * @throws SecurityException if the caller is not the active scorer or privileged.
+ */
+ public boolean clearScores() throws SecurityException {
+ try {
+ return mService.clearScores();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Set the active scorer to a new package and clear existing scores.
+ *
+ * @return true if the operation succeeded, or false if the new package is not a valid scorer.
+ * @throws SecurityException if the caller does not hold the
+ * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission indicating
+ * that it can manage scorer applications.
+ * @hide
+ */
+ public boolean setActiveScorer(String packageName) throws SecurityException {
+ try {
+ return mService.setActiveScorer(packageName);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Request scoring for networks.
+ *
+ * <p>Note that this is just a helper method to assemble the broadcast, and will run in the
+ * calling process.
+ *
+ * @return true if the broadcast was sent, or false if there is no active scorer.
+ * @throws SecurityException if the caller does not hold the
+ * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission.
+ * @hide
+ */
+ public boolean requestScores(NetworkKey[] networks) throws SecurityException {
+ String activeScorer = getActiveScorerPackage();
+ if (activeScorer == null) {
+ return false;
+ }
+ Intent intent = new Intent(ACTION_SCORE_NETWORKS);
+ intent.setPackage(activeScorer);
+ intent.putExtra(EXTRA_NETWORKS_TO_SCORE, networks);
+ mContext.sendBroadcast(intent);
+ return true;
+ }
+
+ /**
+ * Register a network score cache.
+ *
+ * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
+ * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores.
+ * @throws SecurityException if the caller does not hold the
+ * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission.
+ * @throws IllegalArgumentException if a score cache is already registered for this type.
+ * @hide
+ */
+ public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
+ try {
+ mService.registerNetworkScoreCache(networkType, scoreCache);
+ } catch (RemoteException e) {
+ }
+ }
+}
diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java
new file mode 100644
index 0000000..3660e7a
--- /dev/null
+++ b/core/java/android/net/NetworkScorerAppManager.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.net;
+
+import android.Manifest.permission;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Internal class for managing the primary network scorer application.
+ *
+ * @hide
+ */
+public final class NetworkScorerAppManager {
+ private static final String TAG = "NetworkScorerAppManager";
+
+ private static final Intent SCORE_INTENT =
+ new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS);
+
+ /** This class cannot be instantiated. */
+ private NetworkScorerAppManager() {}
+
+ /**
+ * Returns the list of available scorer app package names.
+ *
+ * <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 NetworkScoreManager#ACTION_SCORE_NETWORKS} guarded by the
+ * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission.
+ * </ul>
+ *
+ * @return the list of scorers, or the empty list if there are no valid scorers.
+ */
+ public static Collection<String> getAllValidScorers(Context context) {
+ List<String> scorers = new ArrayList<>();
+
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> receivers = pm.queryBroadcastReceivers(SCORE_INTENT, 0 /* flags */);
+ for (ResolveInfo receiver : receivers) {
+ // This field is a misnomer, see android.content.pm.ResolveInfo#activityInfo
+ final ActivityInfo receiverInfo = receiver.activityInfo;
+ if (receiverInfo == null) {
+ // Should never happen with queryBroadcastReceivers, but invalid nonetheless.
+ continue;
+ }
+ if (!permission.BROADCAST_SCORE_NETWORKS.equals(receiverInfo.permission)) {
+ // Receiver doesn't require the BROADCAST_SCORE_NETWORKS permission, which means
+ // anyone could trigger network scoring and flood the framework with score requests.
+ continue;
+ }
+ if (pm.checkPermission(permission.SCORE_NETWORKS, receiverInfo.packageName) !=
+ PackageManager.PERMISSION_GRANTED) {
+ // Application doesn't hold the SCORE_NETWORKS permission, so the user never
+ // approved it as a network scorer.
+ continue;
+ }
+ scorers.add(receiverInfo.packageName);
+ }
+
+ return scorers;
+ }
+
+ /**
+ * Get the application package name to use for scoring networks.
+ *
+ * @return the scorer package or null if scoring is disabled (including if no scorer was ever
+ * selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because
+ * it was disabled or uninstalled).
+ */
+ public static String getActiveScorer(Context context) {
+ String scorerPackage = Settings.Global.getString(context.getContentResolver(),
+ Global.NETWORK_SCORER_APP);
+ if (isPackageValidScorer(context, scorerPackage)) {
+ return scorerPackage;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Set the specified package as the default scorer application.
+ *
+ * <p>The caller must have permission to write to {@link Settings.Global}.
+ *
+ * @param context the context of the calling application
+ * @param packageName the packageName of the new scorer to use. If null, scoring will be
+ * disabled. Otherwise, the scorer will only be set if it is a valid scorer application.
+ * @return true if the scorer was changed, or false if the package is not a valid scorer.
+ */
+ public static boolean setActiveScorer(Context context, String packageName) {
+ String oldPackageName = Settings.Global.getString(context.getContentResolver(),
+ Settings.Global.NETWORK_SCORER_APP);
+ if (TextUtils.equals(oldPackageName, packageName)) {
+ // No change.
+ return true;
+ }
+
+ Log.i(TAG, "Changing network scorer from " + oldPackageName + " to " + packageName);
+
+ if (packageName == null) {
+ Settings.Global.putString(context.getContentResolver(), Global.NETWORK_SCORER_APP,
+ null);
+ return true;
+ } else {
+ // We only make the change if the new package is valid.
+ if (isPackageValidScorer(context, packageName)) {
+ Settings.Global.putString(context.getContentResolver(),
+ Settings.Global.NETWORK_SCORER_APP, packageName);
+ return true;
+ } else {
+ Log.w(TAG, "Requested network scorer is not valid: " + packageName);
+ return false;
+ }
+ }
+ }
+
+ /** Determine whether the application with the given UID is the enabled scorer. */
+ public static boolean isCallerActiveScorer(Context context, int callingUid) {
+ String defaultApp = getActiveScorer(context);
+ if (defaultApp == null) {
+ return false;
+ }
+ AppOpsManager appOpsMgr = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ try {
+ appOpsMgr.checkPackage(callingUid, defaultApp);
+ return true;
+ } catch (SecurityException e) {
+ return false;
+ }
+ }
+
+ /** Returns true if the given package is a valid scorer. */
+ public static boolean isPackageValidScorer(Context context, String packageName) {
+ Collection<String> applications = getAllValidScorers(context);
+ return packageName != null && applications.contains(packageName);
+ }
+}
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..bea8d1c 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,78 +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();
- }
- }
- }
-
- static class AndroidProxySelectorRoutePlanner
- extends org.apache.http.impl.conn.ProxySelectorRoutePlanner {
-
- private Context mContext;
-
- public AndroidProxySelectorRoutePlanner(SchemeRegistry schreg, ProxySelector prosel,
- Context context) {
- super(schreg, prosel);
- mContext = context;
- }
-
- @Override
- protected java.net.Proxy chooseProxy(List<java.net.Proxy> proxies, HttpHost target,
- HttpRequest request, HttpContext context) {
- return getProxy(mContext, target.getHostName());
- }
-
- @Override
- protected HttpHost determineProxy(HttpHost target, HttpRequest request,
- HttpContext context) {
- return getPreferredHttpHost(mContext, target.getHostName());
- }
-
- @Override
- public HttpRoute determineRoute(HttpHost target, HttpRequest request,
- HttpContext context) {
- HttpHost proxy = getPreferredHttpHost(mContext, target.getHostName());
- if (proxy == null) {
- return new HttpRoute(target);
- } else {
- return new HttpRoute(target, null, proxy, false);
+ return PROXY_PORT_INVALID;
}
+ if (portVal <= 0 || portVal > 0xFFFF) return PROXY_PORT_INVALID;
}
- }
-
- /** @hide */
- public static final HttpRoutePlanner getAndroidProxySelectorRoutePlanner(Context context) {
- AndroidProxySelectorRoutePlanner ret = new AndroidProxySelectorRoutePlanner(
- new SchemeRegistry(), ProxySelector.getDefault(), context);
- return ret;
+ return PROXY_VALID;
}
/** @hide */
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..dd744d3
--- /dev/null
+++ b/core/java/android/net/RssiCurve.java
@@ -0,0 +1,177 @@
+/*
+ * 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.Arrays;
+import java.util.Objects;
+
+/**
+ * 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);
+ }
+
+ /**
+ * Lookup the score for a given RSSI value.
+ *
+ * @param rssi The RSSI to lookup. If the RSSI falls below the start of the curve, the score at
+ * the start of the curve will be returned. If it falls after the end of the curve, the
+ * score at the end of the curve will be returned.
+ * @return the score for the given RSSI.
+ */
+ public byte lookupScore(int rssi) {
+ int index = (rssi - start) / bucketWidth;
+
+ // Snap the index to the closest bucket if it falls outside the curve.
+ if (index < 0) {
+ index = 0;
+ } else if (index > rssiBuckets.length - 1) {
+ index = rssiBuckets.length - 1;
+ }
+
+ return rssiBuckets[index];
+ }
+
+ /**
+ * Determine if two RSSI curves are defined in the same way.
+ *
+ * <p>Note that two curves can be equivalent but defined differently, e.g. if one bucket in one
+ * curve is split into two buckets in another. For the purpose of this method, these curves are
+ * not considered equal to each other.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ RssiCurve rssiCurve = (RssiCurve) o;
+
+ return start == rssiCurve.start &&
+ bucketWidth == rssiCurve.bucketWidth &&
+ Arrays.equals(rssiBuckets, rssiCurve.rssiBuckets);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(start, bucketWidth, 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..7902313
--- /dev/null
+++ b/core/java/android/net/ScoredNetwork.java
@@ -0,0 +1,117 @@
+/*
+ * 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.Objects;
+
+/**
+ * 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 boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ScoredNetwork that = (ScoredNetwork) o;
+
+ return Objects.equals(networkKey, that.networkKey) &&
+ Objects.equals(rssiCurve, that.rssiCurve);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(networkKey, rssiCurve);
+ }
+
+ @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/Uri.java b/core/java/android/net/Uri.java
index a7a8a0a..ce70455 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -21,6 +21,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.StrictMode;
import android.util.Log;
+
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@@ -32,8 +33,10 @@ import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import java.util.RandomAccess;
import java.util.Set;
+
import libcore.net.UriCodec;
/**
@@ -2338,4 +2341,29 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
StrictMode.onFileUriExposed(location);
}
}
+
+ /**
+ * Test if this is a path prefix match against the given Uri. Verifies that
+ * scheme, authority, and atomic path segments match.
+ *
+ * @hide
+ */
+ public boolean isPathPrefixMatch(Uri prefix) {
+ if (!Objects.equals(getScheme(), prefix.getScheme())) return false;
+ if (!Objects.equals(getAuthority(), prefix.getAuthority())) return false;
+
+ List<String> seg = getPathSegments();
+ List<String> prefixSeg = prefix.getPathSegments();
+
+ final int prefixSize = prefixSeg.size();
+ if (seg.size() < prefixSize) return false;
+
+ for (int i = 0; i < prefixSize; i++) {
+ if (!Objects.equals(seg.get(i), prefixSeg.get(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/core/java/android/net/WifiKey.java b/core/java/android/net/WifiKey.java
new file mode 100644
index 0000000..9e92e89
--- /dev/null
+++ b/core/java/android/net/WifiKey.java
@@ -0,0 +1,122 @@
+/*
+ * 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.Objects;
+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 boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ WifiKey wifiKey = (WifiKey) o;
+
+ return Objects.equals(ssid, wifiKey.ssid) && Objects.equals(bssid, wifiKey.bssid);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ssid, 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/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl
index b8a5ba7..ae9796b 100644
--- a/core/java/android/nfc/INfcCardEmulation.aidl
+++ b/core/java/android/nfc/INfcCardEmulation.aidl
@@ -17,6 +17,7 @@
package android.nfc;
import android.content.ComponentName;
+import android.nfc.cardemulation.AidGroup;
import android.nfc.cardemulation.ApduServiceInfo;
import android.os.RemoteCallback;
@@ -29,5 +30,8 @@ interface INfcCardEmulation
boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid);
boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
boolean setDefaultForNextTap(int userHandle, in ComponentName service);
+ boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
+ AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category);
+ boolean removeAidGroupForService(int userHandle, in ComponentName service, String category);
List<ApduServiceInfo> getServices(int userHandle, in String category);
}
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/cardemulation/AidGroup.aidl b/core/java/android/nfc/cardemulation/AidGroup.aidl
new file mode 100644
index 0000000..56d6fa5
--- /dev/null
+++ b/core/java/android/nfc/cardemulation/AidGroup.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.cardemulation;
+
+parcelable AidGroup;
diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java
new file mode 100644
index 0000000..2820f40
--- /dev/null
+++ b/core/java/android/nfc/cardemulation/AidGroup.java
@@ -0,0 +1,165 @@
+package android.nfc.cardemulation;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * The AidGroup class represents a group of ISO/IEC 7816-4
+ * Application Identifiers (AIDs) for a specific application
+ * category, along with a description resource describing
+ * the group.
+ */
+public final class AidGroup implements Parcelable {
+ /**
+ * The maximum number of AIDs that can be present in any one group.
+ */
+ public static final int MAX_NUM_AIDS = 256;
+
+ static final String TAG = "AidGroup";
+
+ final ArrayList<String> aids;
+ final String category;
+ final String description;
+
+ /**
+ * Creates a new AidGroup object.
+ *
+ * @param aids The list of AIDs present in the group
+ * @param category The category of this group
+ */
+ public AidGroup(ArrayList<String> aids, String category) {
+ if (aids == null || aids.size() == 0) {
+ throw new IllegalArgumentException("No AIDS in AID group.");
+ }
+ if (aids.size() > MAX_NUM_AIDS) {
+ throw new IllegalArgumentException("Too many AIDs in AID group.");
+ }
+ if (!isValidCategory(category)) {
+ throw new IllegalArgumentException("Category specified is not valid.");
+ }
+ this.aids = aids;
+ this.category = category;
+ this.description = null;
+ }
+
+ AidGroup(String category, String description) {
+ this.aids = new ArrayList<String>();
+ this.category = category;
+ this.description = description;
+ }
+
+ /**
+ * @return the category of this AID group
+ */
+ public String getCategory() {
+ return category;
+ }
+
+ /**
+ * @return the list of AIDs in this group
+ */
+ public ArrayList<String> getAids() {
+ return aids;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder("Category: " + category +
+ ", AIDs:");
+ for (String aid : aids) {
+ out.append(aid);
+ out.append(", ");
+ }
+ return out.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(category);
+ dest.writeInt(aids.size());
+ if (aids.size() > 0) {
+ dest.writeStringList(aids);
+ }
+ }
+
+ public static final Parcelable.Creator<AidGroup> CREATOR =
+ new Parcelable.Creator<AidGroup>() {
+
+ @Override
+ public AidGroup createFromParcel(Parcel source) {
+ String category = source.readString();
+ int listSize = source.readInt();
+ ArrayList<String> aidList = new ArrayList<String>();
+ if (listSize > 0) {
+ source.readStringList(aidList);
+ }
+ return new AidGroup(aidList, category);
+ }
+
+ @Override
+ public AidGroup[] newArray(int size) {
+ return new AidGroup[size];
+ }
+ };
+
+ /**
+ * @hide
+ * Note: description is not serialized, since it's not localized
+ * and resource identifiers don't make sense to persist.
+ */
+ static public AidGroup createFromXml(XmlPullParser parser) throws XmlPullParserException, IOException {
+ String category = parser.getAttributeValue(null, "category");
+ ArrayList<String> aids = new ArrayList<String>();
+ int eventType = parser.getEventType();
+ int minDepth = parser.getDepth();
+ while (eventType != XmlPullParser.END_DOCUMENT && parser.getDepth() >= minDepth) {
+ if (eventType == XmlPullParser.START_TAG) {
+ String tagName = parser.getName();
+ if (tagName.equals("aid")) {
+ String aid = parser.getAttributeValue(null, "value");
+ if (aid != null) {
+ aids.add(aid);
+ }
+ } else {
+ Log.d(TAG, "Ignorning unexpected tag: " + tagName);
+ }
+ }
+ eventType = parser.next();
+ }
+ if (category != null && aids.size() > 0) {
+ return new AidGroup(aids, category);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void writeAsXml(XmlSerializer out) throws IOException {
+ out.attribute(null, "category", category);
+ for (String aid : aids) {
+ out.startTag(null, "aid");
+ out.attribute(null, "value", aid);
+ out.endTag(null, "aid");
+ }
+ }
+
+ boolean isValidCategory(String category) {
+ return CardEmulation.CATEGORY_PAYMENT.equals(category) ||
+ CardEmulation.CATEGORY_OTHER.equals(category);
+ }
+}
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index d7ef4bc..94f35ed 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -35,9 +35,12 @@ import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Map;
/**
* @hide
@@ -56,24 +59,19 @@ public final class ApduServiceInfo implements Parcelable {
final String mDescription;
/**
- * Convenience AID list
- */
- final ArrayList<String> mAids;
-
- /**
* Whether this service represents AIDs running on the host CPU
*/
final boolean mOnHost;
/**
- * All AID groups this service handles
+ * Mapping from category to static AID group
*/
- final ArrayList<AidGroup> mAidGroups;
+ final HashMap<String, AidGroup> mStaticAidGroups;
/**
- * Convenience hashmap
+ * Mapping from category to dynamic AID group
*/
- final HashMap<String, AidGroup> mCategoryToGroup;
+ final HashMap<String, AidGroup> mDynamicAidGroups;
/**
* Whether this service should only be started when the device is unlocked.
@@ -86,26 +84,33 @@ public final class ApduServiceInfo implements Parcelable {
final int mBannerResourceId;
/**
+ * The uid of the package the service belongs to
+ */
+ final int mUid;
+ /**
* @hide
*/
public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
- ArrayList<AidGroup> aidGroups, boolean requiresUnlock, int bannerResource) {
+ ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
+ boolean requiresUnlock, int bannerResource, int uid) {
this.mService = info;
this.mDescription = description;
- this.mAidGroups = aidGroups;
- this.mAids = new ArrayList<String>();
- this.mCategoryToGroup = new HashMap<String, AidGroup>();
+ this.mStaticAidGroups = new HashMap<String, AidGroup>();
+ this.mDynamicAidGroups = new HashMap<String, AidGroup>();
this.mOnHost = onHost;
this.mRequiresDeviceUnlock = requiresUnlock;
- for (AidGroup aidGroup : aidGroups) {
- this.mCategoryToGroup.put(aidGroup.category, aidGroup);
- this.mAids.addAll(aidGroup.aids);
+ for (AidGroup aidGroup : staticAidGroups) {
+ this.mStaticAidGroups.put(aidGroup.category, aidGroup);
+ }
+ for (AidGroup aidGroup : dynamicAidGroups) {
+ this.mDynamicAidGroups.put(aidGroup.category, aidGroup);
}
this.mBannerResourceId = bannerResource;
+ this.mUid = uid;
}
- public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost)
- throws XmlPullParserException, IOException {
+ public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost) throws
+ XmlPullParserException, IOException {
ServiceInfo si = info.serviceInfo;
XmlResourceParser parser = null;
try {
@@ -163,10 +168,10 @@ public final class ApduServiceInfo implements Parcelable {
sa.recycle();
}
- mAidGroups = new ArrayList<AidGroup>();
- mCategoryToGroup = new HashMap<String, AidGroup>();
- mAids = new ArrayList<String>();
+ mStaticAidGroups = new HashMap<String, AidGroup>();
+ mDynamicAidGroups = new HashMap<String, AidGroup>();
mOnHost = onHost;
+
final int depth = parser.getDepth();
AidGroup currentGroup = null;
@@ -179,14 +184,14 @@ public final class ApduServiceInfo implements Parcelable {
final TypedArray groupAttrs = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AidGroup);
// Get category of AID group
- String groupDescription = groupAttrs.getString(
- com.android.internal.R.styleable.AidGroup_description);
String groupCategory = groupAttrs.getString(
com.android.internal.R.styleable.AidGroup_category);
+ String groupDescription = groupAttrs.getString(
+ com.android.internal.R.styleable.AidGroup_description);
if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) {
groupCategory = CardEmulation.CATEGORY_OTHER;
}
- currentGroup = mCategoryToGroup.get(groupCategory);
+ currentGroup = mStaticAidGroups.get(groupCategory);
if (currentGroup != null) {
if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
Log.e(TAG, "Not allowing multiple aid-groups in the " +
@@ -200,9 +205,8 @@ public final class ApduServiceInfo implements Parcelable {
} else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) &&
currentGroup != null) {
if (currentGroup.aids.size() > 0) {
- if (!mCategoryToGroup.containsKey(currentGroup.category)) {
- mAidGroups.add(currentGroup);
- mCategoryToGroup.put(currentGroup.category, currentGroup);
+ if (!mStaticAidGroups.containsKey(currentGroup.category)) {
+ mStaticAidGroups.put(currentGroup.category, currentGroup);
}
} else {
Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs");
@@ -216,7 +220,6 @@ public final class ApduServiceInfo implements Parcelable {
toUpperCase();
if (isValidAid(aid) && !currentGroup.aids.contains(aid)) {
currentGroup.aids.add(aid);
- mAids.add(aid);
} else {
Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
}
@@ -228,6 +231,8 @@ public final class ApduServiceInfo implements Parcelable {
} finally {
if (parser != null) parser.close();
}
+ // Set uid
+ mUid = si.applicationInfo.uid;
}
public ComponentName getComponent() {
@@ -235,16 +240,58 @@ public final class ApduServiceInfo implements Parcelable {
mService.serviceInfo.name);
}
+ /**
+ * Returns a consolidated list of AIDs from the AID groups
+ * registered by this service. Note that if a service has both
+ * a static (manifest-based) AID group for a category and a dynamic
+ * AID group, only the dynamically registered AIDs will be returned
+ * for that category.
+ * @return List of AIDs registered by the service
+ */
public ArrayList<String> getAids() {
- return mAids;
+ final ArrayList<String> aids = new ArrayList<String>();
+ for (AidGroup group : getAidGroups()) {
+ aids.addAll(group.aids);
+ }
+ return aids;
}
+ /**
+ * Returns the registered AID group for this category.
+ */
+ public AidGroup getDynamicAidGroupForCategory(String category) {
+ return mDynamicAidGroups.get(category);
+ }
+
+ public boolean removeDynamicAidGroupForCategory(String category) {
+ return (mDynamicAidGroups.remove(category) != null);
+ }
+
+ /**
+ * Returns a consolidated list of AID groups
+ * registered by this service. Note that if a service has both
+ * a static (manifest-based) AID group for a category and a dynamic
+ * AID group, only the dynamically registered AID group will be returned
+ * for that category.
+ * @return List of AIDs registered by the service
+ */
public ArrayList<AidGroup> getAidGroups() {
- return mAidGroups;
+ final ArrayList<AidGroup> groups = new ArrayList<AidGroup>();
+ for (Map.Entry<String, AidGroup> entry : mDynamicAidGroups.entrySet()) {
+ groups.add(entry.getValue());
+ }
+ for (Map.Entry<String, AidGroup> entry : mStaticAidGroups.entrySet()) {
+ if (!mDynamicAidGroups.containsKey(entry.getKey())) {
+ // Consolidate AID groups - don't return static ones
+ // if a dynamic group exists for the category.
+ groups.add(entry.getValue());
+ }
+ }
+ return groups;
}
public boolean hasCategory(String category) {
- return mCategoryToGroup.containsKey(category);
+ return (mStaticAidGroups.containsKey(category) || mDynamicAidGroups.containsKey(category));
}
public boolean isOnHost() {
@@ -259,6 +306,14 @@ public final class ApduServiceInfo implements Parcelable {
return mDescription;
}
+ public int getUid() {
+ return mUid;
+ }
+
+ public void setOrReplaceDynamicAidGroup(AidGroup aidGroup) {
+ mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
+ }
+
public CharSequence loadLabel(PackageManager pm) {
return mService.loadLabel(pm);
}
@@ -304,8 +359,12 @@ public final class ApduServiceInfo implements Parcelable {
StringBuilder out = new StringBuilder("ApduService: ");
out.append(getComponent());
out.append(", description: " + mDescription);
- out.append(", AID Groups: ");
- for (AidGroup aidGroup : mAidGroups) {
+ out.append(", Static AID Groups: ");
+ for (AidGroup aidGroup : mStaticAidGroups.values()) {
+ out.append(aidGroup.toString());
+ }
+ out.append(", Dynamic AID Groups: ");
+ for (AidGroup aidGroup : mDynamicAidGroups.values()) {
out.append(aidGroup.toString());
}
return out.toString();
@@ -336,12 +395,17 @@ public final class ApduServiceInfo implements Parcelable {
mService.writeToParcel(dest, flags);
dest.writeString(mDescription);
dest.writeInt(mOnHost ? 1 : 0);
- dest.writeInt(mAidGroups.size());
- if (mAidGroups.size() > 0) {
- dest.writeTypedList(mAidGroups);
+ dest.writeInt(mStaticAidGroups.size());
+ if (mStaticAidGroups.size() > 0) {
+ dest.writeTypedList(new ArrayList<AidGroup>(mStaticAidGroups.values()));
+ }
+ dest.writeInt(mDynamicAidGroups.size());
+ if (mDynamicAidGroups.size() > 0) {
+ dest.writeTypedList(new ArrayList<AidGroup>(mDynamicAidGroups.values()));
}
dest.writeInt(mRequiresDeviceUnlock ? 1 : 0);
dest.writeInt(mBannerResourceId);
+ dest.writeInt(mUid);
};
public static final Parcelable.Creator<ApduServiceInfo> CREATOR =
@@ -351,14 +415,21 @@ public final class ApduServiceInfo implements Parcelable {
ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
String description = source.readString();
boolean onHost = (source.readInt() != 0) ? true : false;
- ArrayList<AidGroup> aidGroups = new ArrayList<AidGroup>();
- int numGroups = source.readInt();
- if (numGroups > 0) {
- source.readTypedList(aidGroups, AidGroup.CREATOR);
+ ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>();
+ int numStaticGroups = source.readInt();
+ if (numStaticGroups > 0) {
+ source.readTypedList(staticAidGroups, AidGroup.CREATOR);
+ }
+ ArrayList<AidGroup> dynamicAidGroups = new ArrayList<AidGroup>();
+ int numDynamicGroups = source.readInt();
+ if (numDynamicGroups > 0) {
+ source.readTypedList(dynamicAidGroups, AidGroup.CREATOR);
}
boolean requiresUnlock = (source.readInt() != 0) ? true : false;
int bannerResource = source.readInt();
- return new ApduServiceInfo(info, onHost, description, aidGroups, requiresUnlock, bannerResource);
+ int uid = source.readInt();
+ return new ApduServiceInfo(info, onHost, description, staticAidGroups,
+ dynamicAidGroups, requiresUnlock, bannerResource, uid);
}
@Override
@@ -367,76 +438,22 @@ public final class ApduServiceInfo implements Parcelable {
}
};
- public static class AidGroup implements Parcelable {
- final ArrayList<String> aids;
- final String category;
- final String description;
-
- AidGroup(ArrayList<String> aids, String category, String description) {
- this.aids = aids;
- this.category = category;
- this.description = description;
- }
-
- AidGroup(String category, String description) {
- this.aids = new ArrayList<String>();
- this.category = category;
- this.description = description;
- }
-
- public String getCategory() {
- return category;
- }
-
- public ArrayList<String> getAids() {
- return aids;
- }
-
- @Override
- public String toString() {
- StringBuilder out = new StringBuilder("Category: " + category +
- ", description: " + description + ", AIDs:");
- for (String aid : aids) {
- out.append(aid);
- out.append(", ");
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(" " + getComponent() +
+ " (Description: " + getDescription() + ")");
+ pw.println(" Static AID groups:");
+ for (AidGroup group : mStaticAidGroups.values()) {
+ pw.println(" Category: " + group.category);
+ for (String aid : group.aids) {
+ pw.println(" AID: " + aid);
}
- return out.toString();
}
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(category);
- dest.writeString(description);
- dest.writeInt(aids.size());
- if (aids.size() > 0) {
- dest.writeStringList(aids);
+ pw.println(" Dynamic AID groups:");
+ for (AidGroup group : mDynamicAidGroups.values()) {
+ pw.println(" Category: " + group.category);
+ for (String aid : group.aids) {
+ pw.println(" AID: " + aid);
}
}
-
- public static final Parcelable.Creator<ApduServiceInfo.AidGroup> CREATOR =
- new Parcelable.Creator<ApduServiceInfo.AidGroup>() {
-
- @Override
- public AidGroup createFromParcel(Parcel source) {
- String category = source.readString();
- String description = source.readString();
- int listSize = source.readInt();
- ArrayList<String> aidList = new ArrayList<String>();
- if (listSize > 0) {
- source.readStringList(aidList);
- }
- return new AidGroup(aidList, category, description);
- }
-
- @Override
- public AidGroup[] newArray(int size) {
- return new AidGroup[size];
- }
- };
}
}
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 58d9616..41f039c 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -168,6 +168,10 @@ public final class CardEmulation {
if (manager == null) {
// Get card emu service
INfcCardEmulation service = adapter.getCardEmulationService();
+ if (service == null) {
+ Log.e(TAG, "This device does not implement the INfcCardEmulation interface.");
+ throw new UnsupportedOperationException();
+ }
manager = new CardEmulation(context, service);
sCardEmus.put(context, manager);
}
@@ -271,6 +275,109 @@ public final class CardEmulation {
}
/**
+ * Registers a group of AIDs for the specified service.
+ *
+ * <p>If an AID group for that category was previously
+ * registered for this service (either statically
+ * through the manifest, or dynamically by using this API),
+ * that AID group will be replaced with this one.
+ *
+ * <p>Note that you can only register AIDs for a service that
+ * is running under the same UID as you are. Typically
+ * this means you need to call this from the same
+ * package as the service itself, though UIDs can also
+ * be shared between packages using shared UIDs.
+ *
+ * @param service The component name of the service
+ * @param aidGroup The group of AIDs to be registered
+ * @return whether the registration was successful.
+ */
+ public boolean registerAidGroupForService(ComponentName service, AidGroup aidGroup) {
+ try {
+ return sService.registerAidGroupForService(UserHandle.myUserId(), service, aidGroup);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.registerAidGroupForService(UserHandle.myUserId(), service,
+ aidGroup);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Retrieves the currently registered AID group for the specified
+ * category for a service.
+ *
+ * <p>Note that this will only return AID groups that were dynamically
+ * registered using {@link #registerAidGroupForService(ComponentName, AidGroup)}
+ * method. It will *not* return AID groups that were statically registered
+ * in the manifest.
+ *
+ * @param service The component name of the service
+ * @param category The category of the AID group to be returned, e.g. {@link #CATEGORY_PAYMENT}
+ * @return The AID group, or null if it couldn't be found
+ */
+ public AidGroup getAidGroupForService(ComponentName service, String category) {
+ try {
+ return sService.getAidGroupForService(UserHandle.myUserId(), service, category);
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ try {
+ return sService.getAidGroupForService(UserHandle.myUserId(), service, category);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Removes a registered AID group for the specified category for the
+ * service provided.
+ *
+ * <p>Note that this will only remove AID groups that were dynamically
+ * registered using the {@link #registerAidGroupForService(ComponentName, AidGroup)}
+ * method. It will *not* remove AID groups that were statically registered in
+ * the manifest. If a dynamically registered AID group is removed using
+ * this method, and a statically registered AID group for the same category
+ * exists in the manifest, that AID group will become active again.
+ *
+ * @param service The component name of the service
+ * @param category The category of the AID group to be removed, e.g. {@link #CATEGORY_PAYMENT}
+ * @return whether the group was successfully removed.
+ */
+ public boolean removeAidGroupForService(ComponentName service, String category) {
+ try {
+ return sService.removeAidGroupForService(UserHandle.myUserId(), service, category);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.removeAidGroupForService(UserHandle.myUserId(), service, category);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
* @hide
*/
public boolean setDefaultServiceForCategory(ComponentName service, String category) {
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..9e9820f 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,25 +118,20 @@ 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",
// and "unplugged". They were shortened for efficiency sake.
- private static final String[] STAT_NAMES = { "t", "l", "c", "u" };
-
+ private static final String[] STAT_NAMES = { "l", "c", "u" };
+
/**
* Bump the version on this if the checkin format changes.
*/
@@ -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,14 @@ 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 static final String DISCHARGE_STEP_DATA = "dsd";
+ private static final String CHARGE_STEP_DATA = "csd";
private final StringBuilder mFormatBuilder = new StringBuilder(32);
private final Formatter mFormatter = new Formatter(mFormatBuilder);
@@ -180,7 +188,7 @@ public abstract class BatteryStats implements Parcelable {
* Returns the count associated with this Counter for the
* selected type of statistics.
*
- * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
*/
public abstract int getCountLocked(int which);
@@ -191,6 +199,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_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, 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 {
@@ -199,7 +226,7 @@ public abstract class BatteryStats implements Parcelable {
* Returns the count associated with this Timer for the
* selected type of statistics.
*
- * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
*/
public abstract int getCountLocked(int which);
@@ -207,11 +234,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 which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT
+ * @param elapsedRealtimeUs current elapsed realtime of system in microseconds
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, 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 +296,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 +340,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 +360,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,29 +380,34 @@ 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.
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
*/
public abstract long getUserTime(int which);
/**
* Returns the total time (in 1/100 sec) spent executing in system code.
*
- * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
*/
public abstract long getSystemTime(int which);
/**
* Returns the number of times the process has been started.
*
- * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
*/
public abstract int getStarts(int which);
/**
* Returns the cpu time spent in microseconds while the process was in the foreground.
- * @param which one of STATS_TOTAL, STATS_LAST, STATS_CURRENT or STATS_UNPLUGGED
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
* @return foreground cpu time in microseconds
*/
public abstract long getForegroundTime(int which);
@@ -381,7 +416,7 @@ public abstract class BatteryStats implements Parcelable {
* Returns the approximate cpu time spent in microseconds, at a certain CPU speed.
* @param speedStep the index of the CPU speed. This is not the actual speed of the
* CPU.
- * @param which one of STATS_TOTAL, STATS_LAST, STATS_CURRENT or STATS_UNPLUGGED
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
* @see BatteryStats#getCpuSpeedSteps()
*/
public abstract long getTimeAtCpuSpeedStep(int speedStep, int which);
@@ -400,7 +435,7 @@ public abstract class BatteryStats implements Parcelable {
* Returns the number of times this package has done something that could wake up the
* device from sleep.
*
- * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
*/
public abstract int getWakeups(int which);
@@ -418,7 +453,7 @@ public abstract class BatteryStats implements Parcelable {
* Returns the amount of time spent started.
*
* @param batteryUptime elapsed uptime on battery in microseconds.
- * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
* @return
*/
public abstract long getStartTime(long batteryUptime, int which);
@@ -426,35 +461,90 @@ public abstract class BatteryStats implements Parcelable {
/**
* Returns the total number of times startService() has been called.
*
- * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
*/
public abstract int getStarts(int which);
/**
* Returns the total number times the service has been launched.
*
- * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
*/
public abstract int getLaunches(int which);
}
}
}
+ 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 +554,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 +662,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);
- }
+ wakelockTag = null;
}
- if (batteryLevelIntChanged) {
- dest.writeInt(batteryLevelInt);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
- + Integer.toHexString(batteryLevelInt)
- + " batteryLevel=" + batteryLevel
- + " batteryTemp=" + batteryTemperature
- + " batteryVolt=" + (int)batteryVoltage);
- }
- 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 +738,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 +765,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 +836,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 +900,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 +919,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 +932,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 +942,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 +967,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 +976,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 +1049,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 +1058,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 +1113,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 +1121,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 +1160,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;
- public static final int NUM_NETWORK_ACTIVITY_TYPES = NETWORK_WIFI_TX_BYTES + 1;
+ static final String[] BLUETOOTH_STATE_NAMES = {
+ "inactive", "low", "med", "high"
+ };
- public abstract long getNetworkActivityCount(int type, int which);
+ 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);
+
+ /**
+ * Returns the number of times that Bluetooth has entered the given active state.
+ *
+ * {@hide}
+ */
+ public abstract int getBluetoothStateCount(int bluetoothState, int which);
+
+ public static final int NETWORK_MOBILE_RX_DATA = 0;
+ public static final int NETWORK_MOBILE_TX_DATA = 1;
+ public static final int NETWORK_WIFI_RX_DATA = 2;
+ public static final int NETWORK_WIFI_TX_DATA = 3;
+
+ public static final int NUM_NETWORK_ACTIVITY_TYPES = NETWORK_WIFI_TX_DATA + 1;
+
+ public abstract long getNetworkActivityBytes(int type, int which);
+ public abstract long getNetworkActivityPackets(int type, int which);
+
+ /**
+ * Return the wall clock time when battery stats data collection started.
+ */
+ public abstract long getStartClockTime();
/**
* Return whether we are currently running on battery.
@@ -964,19 +1224,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 +1255,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.
*/
@@ -1035,7 +1287,7 @@ public abstract class BatteryStats implements Parcelable {
* Returns the total, last, or current battery uptime in microseconds.
*
* @param curTime the elapsed realtime in microseconds.
- * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
*/
public abstract long computeBatteryUptime(long curTime, int which);
@@ -1043,31 +1295,95 @@ public abstract class BatteryStats implements Parcelable {
* Returns the total, last, or current battery realtime in microseconds.
*
* @param curTime the current elapsed realtime in microseconds.
- * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
*/
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_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, 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_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, 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.
- * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
*/
public abstract long computeUptime(long curTime, int which);
/**
* Returns the total, last, or current realtime in microseconds.
- * *
+ *
* @param curTime the current elapsed realtime in microseconds.
- * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
*/
public abstract long computeRealtime(long curTime, int which);
-
+
+ /**
+ * Compute an approximation for how much run time (in microseconds) is remaining on
+ * the battery. Returns -1 if no time can be computed: either there is not
+ * enough current data to make a decision, or the battery is currently
+ * charging.
+ *
+ * @param curTime The current elepsed realtime in microseconds.
+ */
+ public abstract long computeBatteryTimeRemaining(long curTime);
+
+ /**
+ * Return the historical number of discharge steps we currently have.
+ */
+ public abstract int getNumDischargeStepDurations();
+
+ /**
+ * Return the array of discharge step durations; the number of valid
+ * items in it is returned by {@link #getNumDischargeStepDurations()}.
+ * These values are in milliseconds.
+ */
+ public abstract long[] getDischargeStepDurationsArray();
+
+ /**
+ * Compute an approximation for how much time (in microseconds) remains until the battery
+ * is fully charged. Returns -1 if no time can be computed: either there is not
+ * enough current data to make a decision, or the battery is currently
+ * discharging.
+ *
+ * @param curTime The current elepsed realtime in microseconds.
+ */
+ public abstract long computeChargeTimeRemaining(long curTime);
+
+ /**
+ * Return the historical number of charge steps we currently have.
+ */
+ public abstract int getNumChargeStepDurations();
+
+ /**
+ * Return the array of charge step durations; the number of valid
+ * items in it is returned by {@link #getNumChargeStepDurations()}.
+ * These values are in milliseconds.
+ */
+ public abstract long[] getChargeStepDurationsArray();
+
+ 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 +1412,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 +1443,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 +1460,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 +1474,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 which which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, 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 +1508,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 which which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, 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 +1557,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 +1585,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 +1604,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 +1710,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, false);
+ 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 +1790,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 +1841,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 +1867,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 +1879,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 +1889,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 +1957,27 @@ 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);
+ final long batteryTimeRemaining = computeBatteryTimeRemaining(rawRealtime);
+ final long chargeTimeRemaining = computeChargeTimeRemaining(rawRealtime);
+
StringBuilder sb = new StringBuilder(128);
SparseArray<? extends Uid> uidStats = getUidStats();
@@ -1556,37 +1995,69 @@ 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, ");
- 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);
+ sb.append("uptime");
+ if (batteryTimeRemaining >= 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Battery time remaining: ");
+ formatTimeMs(sb, batteryTimeRemaining / 1000);
+ pw.println(sb.toString());
+ }
+ if (chargeTimeRemaining >= 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Charge time remaining: ");
+ formatTimeMs(sb, chargeTimeRemaining / 1000);
+ pw.println(sb.toString());
+ }
+ 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 +2066,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 +2087,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 +2109,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));
+ 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(" Total full wakelock time: "); formatTimeMs(sb,
- (fullWakeLockTimeTotalMicros + 500) / 1000);
- sb.append(", Total partial wakelock time: "); formatTimeMs(sb,
- (partialWakeLockTimeTotalMicros + 500) / 1000);
- pw.println(sb.toString());
-
- 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 +2160,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 +2190,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 +2329,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, false);
+ 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 +2521,70 @@ public abstract class BatteryStats implements Parcelable {
UserHandle.formatUid(pw, uid);
pw.println(":");
boolean uidActivity = false;
-
- long mobileRxBytes = u.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, which);
- long mobileTxBytes = u.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, which);
- long wifiRxBytes = u.getNetworkActivityCount(NETWORK_WIFI_RX_BYTES, which);
- long wifiTxBytes = u.getNetworkActivityCount(NETWORK_WIFI_TX_BYTES, which);
- long fullWifiLockOnTime = u.getFullWifiLockTime(batteryRealtime, which);
- long wifiScanTime = u.getWifiScanTime(batteryRealtime, which);
- long uidWifiRunningTime = u.getWifiRunningTime(batteryRealtime, which);
-
- if (mobileRxBytes > 0 || mobileTxBytes > 0) {
+
+ long mobileRxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which);
+ long mobileTxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which);
+ long wifiRxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which);
+ long wifiTxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which);
+ long mobileRxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which);
+ long mobileTxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which);
+ long uidMobileActiveTime = u.getMobileRadioActiveTime(which);
+ int uidMobileActiveCount = u.getMobileRadioActiveCount(which);
+ long wifiRxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which);
+ long wifiTxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which);
+ long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which);
+ long wifiScanTime = u.getWifiScanTime(rawRealtime, which);
+ long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
+
+ if (mobileRxBytes > 0 || mobileTxBytes > 0
+ || mobileRxPackets > 0 || mobileTxPackets > 0) {
pw.print(prefix); pw.print(" Mobile network: ");
pw.print(formatBytesLocked(mobileRxBytes)); pw.print(" received, ");
- pw.print(formatBytesLocked(mobileTxBytes)); pw.println(" sent");
+ pw.print(formatBytesLocked(mobileTxBytes));
+ pw.print(" sent (packets "); pw.print(mobileRxPackets);
+ pw.print(" received, "); pw.print(mobileTxPackets); pw.println(" sent)");
}
- if (wifiRxBytes > 0 || wifiTxBytes > 0) {
+ if (uidMobileActiveTime > 0 || uidMobileActiveCount > 0) {
+ sb.setLength(0);
+ sb.append(prefix); sb.append(" Mobile radio active: ");
+ formatTimeMs(sb, uidMobileActiveTime / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(uidMobileActiveTime, mobileActiveTime));
+ sb.append(") "); sb.append(uidMobileActiveCount); sb.append("x");
+ long packets = mobileRxPackets + mobileTxPackets;
+ if (packets == 0) {
+ packets = 1;
+ }
+ sb.append(" @ ");
+ sb.append(BatteryStatsHelper.makemAh(uidMobileActiveTime / 1000 / (double)packets));
+ sb.append(" mspp");
+ pw.println(sb.toString());
+ }
+
+ if (wifiRxBytes > 0 || wifiTxBytes > 0 || wifiRxPackets > 0 || wifiTxPackets > 0) {
pw.print(prefix); pw.print(" Wi-Fi network: ");
pw.print(formatBytesLocked(wifiRxBytes)); pw.print(" received, ");
- pw.print(formatBytesLocked(wifiTxBytes)); pw.println(" sent");
+ pw.print(formatBytesLocked(wifiTxBytes));
+ pw.print(" sent (packets "); pw.print(wifiRxPackets);
+ pw.print(" received, "); pw.print(wifiTxPackets); pw.println(" sent)");
+ }
+
+ if (fullWifiLockOnTime != 0 || wifiScanTime != 0
+ || uidWifiRunningTime != 0) {
+ sb.setLength(0);
+ sb.append(prefix); sb.append(" Wifi Running: ");
+ formatTimeMs(sb, uidWifiRunningTime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(uidWifiRunningTime,
+ whichBatteryRealtime)); sb.append(")\n");
+ sb.append(prefix); sb.append(" Full Wifi Lock: ");
+ formatTimeMs(sb, fullWifiLockOnTime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(fullWifiLockOnTime,
+ whichBatteryRealtime)); sb.append(")\n");
+ sb.append(prefix); sb.append(" Wifi Scan: ");
+ formatTimeMs(sb, wifiScanTime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(wifiScanTime,
+ whichBatteryRealtime)); sb.append(")");
+ pw.println(sb.toString());
}
if (u.hasUserActivity()) {
@@ -1881,24 +2608,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 +2621,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 +2635,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 +2695,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 +2719,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 +2738,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 +2860,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 +2914,95 @@ 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;
+ long firstTime = -1;
+
+ public void printNextItem(PrintWriter pw, HistoryItem rec, long baseTime, boolean checkin,
+ boolean verbose) {
+ if (!checkin) {
+ pw.print(" ");
+ TimeUtils.formatDuration(rec.time - baseTime, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
+ pw.print(" (");
+ pw.print(rec.numReadInts);
+ pw.print(") ");
+ } else {
+ if (lastTime < 0) {
+ pw.print(rec.time - baseTime);
+ } 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 +3011,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 +3041,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 +3062,337 @@ 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");
+ 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);
+ }
+
+ private static boolean dumpDurationSteps(PrintWriter pw, String header, long[] steps,
+ int count, boolean checkin) {
+ if (count <= 0) {
+ return false;
+ }
+ if (!checkin) {
+ pw.println(header);
+ }
+ String[] lineArgs = new String[1];
+ for (int i=0; i<count; i++) {
+ if (checkin) {
+ lineArgs[0] = Long.toString(steps[i]);
+ dumpLine(pw, 0 /* uid */, "i" /* category */, header, (Object[])lineArgs);
} 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);
+ pw.print(" #"); pw.print(i); pw.print(": ");
+ TimeUtils.formatDuration(steps[i], pw);
+ pw.println();
}
}
+ return true;
}
+ 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;
- final HistoryItem rec = new HistoryItem();
- if (startIteratingHistoryLocked()) {
- pw.println("Battery History:");
- HistoryPrinter hprinter = new HistoryPrinter();
- while (getNextHistoryLocked(rec)) {
- hprinter.printNextItem(pw, rec, now);
+ 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;
+ long baseTime = -1;
+ boolean printed = false;
+ while (getNextHistoryLocked(rec)) {
+ lastTime = rec.time;
+ if (baseTime < 0) {
+ baseTime = lastTime;
+ }
+ if (rec.time >= histStart) {
+ if (histStart >= 0 && !printed) {
+ if (rec.cmd == HistoryItem.CMD_CURRENT_TIME) {
+ printed = true;
+ } else if (rec.currentTime != 0) {
+ printed = true;
+ byte cmd = rec.cmd;
+ rec.cmd = HistoryItem.CMD_CURRENT_TIME;
+ hprinter.printNextItem(pw, rec, baseTime, false,
+ (flags&DUMP_VERBOSE) != 0);
+ rec.cmd = cmd;
+ }
+ }
+ hprinter.printNextItem(pw, rec, baseTime, false,
+ (flags&DUMP_VERBOSE) != 0);
+ }
+ }
+ if (histStart >= 0) {
+ pw.print(" NEXT: "); pw.println(lastTime+1);
+ }
+ pw.println();
+ } finally {
+ finishIteratingHistoryLocked();
+ }
}
- finishIteratingHistoryLocked();
- pw.println("");
- }
- if (startIteratingOldHistoryLocked()) {
- pw.println("Old battery History:");
- HistoryPrinter hprinter = new HistoryPrinter();
- while (getNextOldHistoryLocked(rec)) {
- hprinter.printNextItem(pw, rec, now);
+ if (startIteratingOldHistoryLocked()) {
+ try {
+ pw.println("Old battery History:");
+ HistoryPrinter hprinter = new HistoryPrinter();
+ long baseTime = -1;
+ while (getNextOldHistoryLocked(rec)) {
+ if (baseTime < 0) {
+ baseTime = rec.time;
+ }
+ hprinter.printNextItem(pw, rec, baseTime, false, (flags&DUMP_VERBOSE) != 0);
+ }
+ pw.println();
+ } finally {
+ finishIteratingOldHistoryLocked();
+ }
}
- finishIteratingOldHistoryLocked();
- pw.println("");
}
-
- 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 && (flags&(DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY)) == 0) {
+ return;
+ }
+
+ 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 (dumpDurationSteps(pw, "Discharge step durations:", getDischargeStepDurationsArray(),
+ getNumDischargeStepDurations(), false)) {
+ long timeRemaining = computeBatteryTimeRemaining(SystemClock.elapsedRealtime());
+ if (timeRemaining >= 0) {
+ pw.print(" Estimated discharge time remaining: ");
+ TimeUtils.formatDuration(timeRemaining / 1000, pw);
+ pw.println();
+ }
+ pw.println();
+ }
+ if (dumpDurationSteps(pw, "Charge step durations:", getChargeStepDurationsArray(),
+ getNumChargeStepDurations(), false)) {
+ long timeRemaining = computeChargeTimeRemaining(SystemClock.elapsedRealtime());
+ if (timeRemaining >= 0) {
+ pw.print(" Estimated charge time remaining: ");
+ TimeUtils.formatDuration(timeRemaining / 1000, pw);
+ pw.println();
+ }
+ 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);
- pw.println("");
+ dumpLocked(context, pw, "", STATS_SINCE_CHARGED, reqUid);
+ pw.println();
+ }
+ if (!filtering || (flags&DUMP_UNPLUGGED_ONLY) != 0) {
+ pw.println("Statistics since last unplugged:");
+ dumpLocked(context, pw, "", STATS_SINCE_UNPLUGGED, reqUid);
}
- pw.println("Statistics since last unplugged:");
- dumpLocked(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(getHistoryTagPoolUid(i));
+ pw.print(",\"");
+ String str = getHistoryTagPoolString(i);
+ str = str.replace("\\", "\\\\");
+ str = str.replace("\"", "\\\"");
+ pw.print(str);
+ pw.print("\"");
+ pw.println();
+ }
+ HistoryPrinter hprinter = new HistoryPrinter();
+ long lastTime = -1;
+ long baseTime = -1;
+ boolean printed = false;
+ while (getNextHistoryLocked(rec)) {
+ lastTime = rec.time;
+ if (baseTime < 0) {
+ baseTime = lastTime;
+ }
+ if (rec.time >= histStart) {
+ if (histStart >= 0 && !printed) {
+ if (rec.cmd == HistoryItem.CMD_CURRENT_TIME) {
+ printed = true;
+ } else if (rec.currentTime != 0) {
+ printed = true;
+ byte cmd = rec.cmd;
+ rec.cmd = HistoryItem.CMD_CURRENT_TIME;
+ pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
+ pw.print(HISTORY_DATA); pw.print(',');
+ hprinter.printNextItem(pw, rec, baseTime, true, false);
+ rec.cmd = cmd;
+ }
+ }
+ pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
+ pw.print(HISTORY_DATA); pw.print(',');
+ hprinter.printNextItem(pw, rec, baseTime, 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 +3420,17 @@ public abstract class BatteryStats implements Parcelable {
}
}
}
- if (isUnpluggedOnly) {
- dumpCheckinLocked(pw, STATS_SINCE_UNPLUGGED, -1);
+ if (!filtering) {
+ dumpDurationSteps(pw, DISCHARGE_STEP_DATA, getDischargeStepDurationsArray(),
+ getNumDischargeStepDurations(), true);
+ dumpDurationSteps(pw, CHARGE_STEP_DATA, getChargeStepDurationsArray(),
+ getNumChargeStepDurations(), true);
+ }
+ 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 fc4fae2..1ca6b90 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -16,12 +16,17 @@
package android.os;
+import android.text.TextUtils;
+import android.util.Slog;
+
import com.android.internal.telephony.TelephonyProperties;
/**
* Information about the current build, extracted from system properties.
*/
public class Build {
+ private static final String TAG = "Build";
+
/** Value used for when a build property is unknown. */
public static final String UNKNOWN = "unknown";
@@ -144,14 +149,22 @@ public class Build {
*/
public static final String CODENAME = getString("ro.build.version.codename");
+ private static final String[] ALL_CODENAMES
+ = getString("ro.build.version.all_codenames").split(",");
+
+ /**
+ * @hide
+ */
+ public static final String[] ACTIVE_CODENAMES = "REL".equals(ALL_CODENAMES[0])
+ ? new String[0] : ALL_CODENAMES;
+
/**
* The SDK version to use when accessing resources.
- * Use the current SDK version code. If we are a development build,
- * also allow the previous SDK version + 1.
+ * Use the current SDK version code. For every active development codename
+ * we are operating under, we bump the assumed resource platform version by 1.
* @hide
*/
- public static final int RESOURCES_SDK_INT = SDK_INT
- + ("REL".equals(CODENAME) ? 0 : 1);
+ public static final int RESOURCES_SDK_INT = SDK_INT + ACTIVE_CODENAMES.length;
}
/**
@@ -505,6 +518,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 implicit
+ * Intent.</li>
+ * </ul>
+ */
+ public static final int L = CUR_DEVELOPMENT;
}
/** The type of build, like "user" or "eng". */
@@ -514,7 +540,43 @@ public class Build {
public static final String TAGS = getString("ro.build.tags");
/** A string that uniquely identifies this build. Do not attempt to parse this value. */
- public static final String FINGERPRINT = getString("ro.build.fingerprint");
+ public static final String FINGERPRINT = deriveFingerprint();
+
+ /**
+ * Some devices split the fingerprint components between multiple
+ * partitions, so we might derive the fingerprint at runtime.
+ */
+ private static String deriveFingerprint() {
+ String finger = SystemProperties.get("ro.build.fingerprint");
+ if (TextUtils.isEmpty(finger)) {
+ finger = getString("ro.product.brand") + '/' +
+ getString("ro.product.name") + '/' +
+ getString("ro.product.device") + ':' +
+ getString("ro.build.version.release") + '/' +
+ getString("ro.build.id") + '/' +
+ getString("ro.build.version.incremental") + ':' +
+ getString("ro.build.type") + '/' +
+ getString("ro.build.tags");
+ }
+ return finger;
+ }
+
+ /**
+ * Ensure that raw fingerprint system property is defined. If it was derived
+ * dynamically by {@link #deriveFingerprint()} this is where we push the
+ * derived value into the property service.
+ *
+ * @hide
+ */
+ public static void ensureFingerprintProperty() {
+ if (TextUtils.isEmpty(SystemProperties.get("ro.build.fingerprint"))) {
+ try {
+ SystemProperties.set("ro.build.fingerprint", FINGERPRINT);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Failed to set fingerprint property", e);
+ }
+ }
+ }
// The following properties only make sense for internal engineering builds.
public static final long TIME = getLong("ro.build.date.utc") * 1000;
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index af57507..c85e418 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -17,7 +17,6 @@
package android.os;
import android.util.ArrayMap;
-import android.util.Log;
import android.util.SparseArray;
import java.io.Serializable;
@@ -29,47 +28,25 @@ import java.util.Set;
* A mapping from String values to various Parcelable types.
*
*/
-public final class Bundle implements Parcelable, Cloneable {
- private static final String TAG = "Bundle";
- static final boolean DEBUG = false;
+public final class Bundle extends CommonBundle {
public static final Bundle EMPTY;
-
- static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L'
static final Parcel EMPTY_PARCEL;
static {
EMPTY = new Bundle();
EMPTY.mMap = ArrayMap.EMPTY;
- EMPTY_PARCEL = Parcel.obtain();
+ EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL;
}
- // Invariant - exactly one of mMap / mParcelledData will be null
- // (except inside a call to unparcel)
-
- /* package */ ArrayMap<String, Object> mMap = null;
-
- /*
- * If mParcelledData is non-null, then mMap will be null and the
- * data are stored as a Parcel containing a Bundle. When the data
- * are unparcelled, mParcelledData willbe set to null.
- */
- /* package */ Parcel mParcelledData = null;
-
private boolean mHasFds = false;
private boolean mFdsKnown = true;
private boolean mAllowFds = true;
/**
- * The ClassLoader used when unparcelling data from mParcelledData.
- */
- private ClassLoader mClassLoader;
-
- /**
* Constructs a new, empty Bundle.
*/
public Bundle() {
- mMap = new ArrayMap<String, Object>();
- mClassLoader = getClass().getClassLoader();
+ super();
}
/**
@@ -79,11 +56,17 @@ public final class Bundle implements Parcelable, Cloneable {
* @param parcelledData a Parcel containing a Bundle
*/
Bundle(Parcel parcelledData) {
- readFromParcel(parcelledData);
+ super(parcelledData);
+
+ mHasFds = mParcelledData.hasFileDescriptors();
+ mFdsKnown = true;
}
/* package */ Bundle(Parcel parcelledData, int length) {
- readFromParcelInner(parcelledData, length);
+ super(parcelledData, length);
+
+ mHasFds = mParcelledData.hasFileDescriptors();
+ mFdsKnown = true;
}
/**
@@ -94,8 +77,7 @@ public final class Bundle implements Parcelable, Cloneable {
* inside of the Bundle.
*/
public Bundle(ClassLoader loader) {
- mMap = new ArrayMap<String, Object>();
- mClassLoader = loader;
+ super(loader);
}
/**
@@ -105,8 +87,7 @@ public final class Bundle implements Parcelable, Cloneable {
* @param capacity the initial capacity of the Bundle
*/
public Bundle(int capacity) {
- mMap = new ArrayMap<String, Object>(capacity);
- mClassLoader = getClass().getClassLoader();
+ super(capacity);
}
/**
@@ -116,27 +97,20 @@ public final class Bundle implements Parcelable, Cloneable {
* @param b a Bundle to be copied.
*/
public Bundle(Bundle b) {
- if (b.mParcelledData != null) {
- if (b.mParcelledData == EMPTY_PARCEL) {
- mParcelledData = EMPTY_PARCEL;
- } else {
- mParcelledData = Parcel.obtain();
- mParcelledData.appendFrom(b.mParcelledData, 0, b.mParcelledData.dataSize());
- mParcelledData.setDataPosition(0);
- }
- } else {
- mParcelledData = null;
- }
-
- if (b.mMap != null) {
- mMap = new ArrayMap<String, Object>(b.mMap);
- } else {
- mMap = null;
- }
+ super(b);
mHasFds = b.mHasFds;
mFdsKnown = b.mFdsKnown;
- mClassLoader = b.mClassLoader;
+ }
+
+ /**
+ * Constructs a Bundle containing a copy of the mappings from the given
+ * PersistableBundle.
+ *
+ * @param b a Bundle to be copied.
+ */
+ public Bundle(PersistableBundle b) {
+ super(b);
}
/**
@@ -145,37 +119,17 @@ public final class Bundle implements Parcelable, Cloneable {
* @hide
*/
public static Bundle forPair(String key, String value) {
- // TODO: optimize this case.
Bundle b = new Bundle(1);
b.putString(key, value);
return b;
}
/**
- * TODO: optimize this later (getting just the value part of a Bundle
- * with a single pair) once Bundle.forPair() above is implemented
- * with a special single-value Map implementation/serialization.
- *
- * Note: value in single-pair Bundle may be null.
- *
* @hide
*/
+ @Override
public String getPairValue() {
- unparcel();
- int size = mMap.size();
- if (size > 1) {
- Log.w(TAG, "getPairValue() used on Bundle with multiple pairs.");
- }
- if (size == 0) {
- return null;
- }
- Object o = mMap.valueAt(0);
- try {
- return (String) o;
- } catch (ClassCastException e) {
- typeWarning("getPairValue()", o, "String", e);
- return null;
- }
+ return super.getPairValue();
}
/**
@@ -184,15 +138,17 @@ public final class Bundle implements Parcelable, Cloneable {
* @param loader An explicit ClassLoader to use when instantiating objects
* inside of the Bundle.
*/
+ @Override
public void setClassLoader(ClassLoader loader) {
- mClassLoader = loader;
+ super.setClassLoader(loader);
}
/**
* Return the ClassLoader currently associated with this Bundle.
*/
+ @Override
public ClassLoader getClassLoader() {
- return mClassLoader;
+ return super.getClassLoader();
}
/** @hide */
@@ -212,52 +168,11 @@ public final class Bundle implements Parcelable, Cloneable {
}
/**
- * If the underlying data are stored as a Parcel, unparcel them
- * using the currently assigned class loader.
- */
- /* package */ synchronized void unparcel() {
- if (mParcelledData == null) {
- if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
- + ": no parcelled data");
- return;
- }
-
- if (mParcelledData == EMPTY_PARCEL) {
- if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
- + ": empty");
- if (mMap == null) {
- mMap = new ArrayMap<String, Object>(1);
- } else {
- mMap.erase();
- }
- mParcelledData = null;
- return;
- }
-
- int N = mParcelledData.readInt();
- if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
- + ": reading " + N + " maps");
- if (N < 0) {
- return;
- }
- if (mMap == null) {
- mMap = new ArrayMap<String, Object>(N);
- } else {
- mMap.erase();
- mMap.ensureCapacity(N);
- }
- mParcelledData.readArrayMapInternal(mMap, N, mClassLoader);
- mParcelledData.recycle();
- mParcelledData = null;
- if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
- + " final map: " + mMap);
- }
-
- /**
* @hide
*/
+ @Override
public boolean isParcelled() {
- return mParcelledData != null;
+ return super.isParcelled();
}
/**
@@ -265,25 +180,26 @@ public final class Bundle implements Parcelable, Cloneable {
*
* @return the number of mappings as an int.
*/
+ @Override
public int size() {
- unparcel();
- return mMap.size();
+ return super.size();
}
/**
* Returns true if the mapping of this Bundle is empty, false otherwise.
*/
+ @Override
public boolean isEmpty() {
- unparcel();
- return mMap.isEmpty();
+ return super.isEmpty();
}
/**
* Removes all elements from the mapping of this Bundle.
*/
+ @Override
public void clear() {
- unparcel();
- mMap.clear();
+ super.clear();
+
mHasFds = false;
mFdsKnown = true;
}
@@ -295,9 +211,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String key
* @return true if the key is part of the mapping, false otherwise
*/
+ @Override
public boolean containsKey(String key) {
- unparcel();
- return mMap.containsKey(key);
+ return super.containsKey(key);
}
/**
@@ -306,9 +222,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String key
* @return an Object, or null
*/
+ @Override
public Object get(String key) {
- unparcel();
- return mMap.get(key);
+ return super.get(key);
}
/**
@@ -316,24 +232,33 @@ public final class Bundle implements Parcelable, Cloneable {
*
* @param key a String key
*/
+ @Override
public void remove(String key) {
- unparcel();
- mMap.remove(key);
+ super.remove(key);
}
/**
* Inserts all mappings from the given Bundle into this Bundle.
*
- * @param map a Bundle
+ * @param bundle a Bundle
*/
- public void putAll(Bundle map) {
+ public void putAll(Bundle bundle) {
unparcel();
- map.unparcel();
- mMap.putAll(map.mMap);
+ bundle.unparcel();
+ mMap.putAll(bundle.mMap);
// fd state is now known if and only if both bundles already knew
- mHasFds |= map.mHasFds;
- mFdsKnown = mFdsKnown && map.mFdsKnown;
+ mHasFds |= bundle.mHasFds;
+ mFdsKnown = mFdsKnown && bundle.mFdsKnown;
+ }
+
+ /**
+ * Inserts all mappings from the given PersistableBundle into this Bundle.
+ *
+ * @param bundle a PersistableBundle
+ */
+ public void putAll(PersistableBundle bundle) {
+ super.putAll(bundle);
}
/**
@@ -341,9 +266,9 @@ public final class Bundle implements Parcelable, Cloneable {
*
* @return a Set of String keys
*/
+ @Override
public Set<String> keySet() {
- unparcel();
- return mMap.keySet();
+ return super.keySet();
}
/**
@@ -352,7 +277,7 @@ public final class Bundle implements Parcelable, Cloneable {
public boolean hasFileDescriptors() {
if (!mFdsKnown) {
boolean fdFound = false; // keep going until we find one or run out of data
-
+
if (mParcelledData != null) {
if (mParcelledData.hasFileDescriptors()) {
fdFound = true;
@@ -390,8 +315,7 @@ public final class Bundle implements Parcelable, Cloneable {
ArrayList array = (ArrayList) obj;
// an ArrayList here might contain either Strings or
// Parcelables; only look inside for Parcelables
- if ((array.size() > 0)
- && (array.get(0) instanceof Parcelable)) {
+ if (!array.isEmpty() && (array.get(0) instanceof Parcelable)) {
for (int n = array.size() - 1; n >= 0; n--) {
Parcelable p = (Parcelable) array.get(n);
if (p != null && ((p.describeContents()
@@ -410,7 +334,7 @@ public final class Bundle implements Parcelable, Cloneable {
}
return mHasFds;
}
-
+
/**
* Inserts a Boolean value into the mapping of this Bundle, replacing
* any existing value for the given key. Either key or value may be null.
@@ -418,9 +342,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a Boolean, or null
*/
+ @Override
public void putBoolean(String key, boolean value) {
- unparcel();
- mMap.put(key, value);
+ super.putBoolean(key, value);
}
/**
@@ -430,9 +354,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a byte
*/
+ @Override
public void putByte(String key, byte value) {
- unparcel();
- mMap.put(key, value);
+ super.putByte(key, value);
}
/**
@@ -442,9 +366,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a char, or null
*/
+ @Override
public void putChar(String key, char value) {
- unparcel();
- mMap.put(key, value);
+ super.putChar(key, value);
}
/**
@@ -454,9 +378,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a short
*/
+ @Override
public void putShort(String key, short value) {
- unparcel();
- mMap.put(key, value);
+ super.putShort(key, value);
}
/**
@@ -466,9 +390,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value an int, or null
*/
+ @Override
public void putInt(String key, int value) {
- unparcel();
- mMap.put(key, value);
+ super.putInt(key, value);
}
/**
@@ -478,9 +402,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a long
*/
+ @Override
public void putLong(String key, long value) {
- unparcel();
- mMap.put(key, value);
+ super.putLong(key, value);
}
/**
@@ -490,9 +414,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a float
*/
+ @Override
public void putFloat(String key, float value) {
- unparcel();
- mMap.put(key, value);
+ super.putFloat(key, value);
}
/**
@@ -502,9 +426,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a double
*/
+ @Override
public void putDouble(String key, double value) {
- unparcel();
- mMap.put(key, value);
+ super.putDouble(key, value);
}
/**
@@ -514,9 +438,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a String, or null
*/
+ @Override
public void putString(String key, String value) {
- unparcel();
- mMap.put(key, value);
+ super.putString(key, value);
}
/**
@@ -526,9 +450,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a CharSequence, or null
*/
+ @Override
public void putCharSequence(String key, CharSequence value) {
- unparcel();
- mMap.put(key, value);
+ super.putCharSequence(key, value);
}
/**
@@ -567,7 +491,7 @@ public final class Bundle implements Parcelable, Cloneable {
* @param value an ArrayList of Parcelable objects, or null
*/
public void putParcelableArrayList(String key,
- ArrayList<? extends Parcelable> value) {
+ ArrayList<? extends Parcelable> value) {
unparcel();
mMap.put(key, value);
mFdsKnown = false;
@@ -602,9 +526,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value an ArrayList<Integer> object, or null
*/
+ @Override
public void putIntegerArrayList(String key, ArrayList<Integer> value) {
- unparcel();
- mMap.put(key, value);
+ super.putIntegerArrayList(key, value);
}
/**
@@ -614,9 +538,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value an ArrayList<String> object, or null
*/
+ @Override
public void putStringArrayList(String key, ArrayList<String> value) {
- unparcel();
- mMap.put(key, value);
+ super.putStringArrayList(key, value);
}
/**
@@ -626,9 +550,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value an ArrayList<CharSequence> object, or null
*/
+ @Override
public void putCharSequenceArrayList(String key, ArrayList<CharSequence> value) {
- unparcel();
- mMap.put(key, value);
+ super.putCharSequenceArrayList(key, value);
}
/**
@@ -638,9 +562,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a Serializable object, or null
*/
+ @Override
public void putSerializable(String key, Serializable value) {
- unparcel();
- mMap.put(key, value);
+ super.putSerializable(key, value);
}
/**
@@ -650,9 +574,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a boolean array object, or null
*/
+ @Override
public void putBooleanArray(String key, boolean[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putBooleanArray(key, value);
}
/**
@@ -662,9 +586,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a byte array object, or null
*/
+ @Override
public void putByteArray(String key, byte[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putByteArray(key, value);
}
/**
@@ -674,9 +598,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a short array object, or null
*/
+ @Override
public void putShortArray(String key, short[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putShortArray(key, value);
}
/**
@@ -686,9 +610,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a char array object, or null
*/
+ @Override
public void putCharArray(String key, char[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putCharArray(key, value);
}
/**
@@ -698,9 +622,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value an int array object, or null
*/
+ @Override
public void putIntArray(String key, int[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putIntArray(key, value);
}
/**
@@ -710,9 +634,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a long array object, or null
*/
+ @Override
public void putLongArray(String key, long[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putLongArray(key, value);
}
/**
@@ -722,9 +646,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a float array object, or null
*/
+ @Override
public void putFloatArray(String key, float[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putFloatArray(key, value);
}
/**
@@ -734,9 +658,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a double array object, or null
*/
+ @Override
public void putDoubleArray(String key, double[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putDoubleArray(key, value);
}
/**
@@ -746,9 +670,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a String array object, or null
*/
+ @Override
public void putStringArray(String key, String[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putStringArray(key, value);
}
/**
@@ -758,9 +682,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a CharSequence array object, or null
*/
+ @Override
public void putCharSequenceArray(String key, CharSequence[] value) {
- unparcel();
- mMap.put(key, value);
+ super.putCharSequenceArray(key, value);
}
/**
@@ -776,6 +700,17 @@ public final class Bundle implements Parcelable, Cloneable {
}
/**
+ * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a Bundle object, or null
+ */
+ public void putPersistableBundle(String key, PersistableBundle value) {
+ super.putPersistableBundle(key, value);
+ }
+
+ /**
* Inserts an {@link IBinder} value into the mapping of this Bundle, replacing
* any existing value for the given key. Either key or value may be null.
*
@@ -817,33 +752,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String
* @return a boolean value
*/
+ @Override
public boolean getBoolean(String key) {
- unparcel();
- if (DEBUG) Log.d(TAG, "Getting boolean in "
- + Integer.toHexString(System.identityHashCode(this)));
- return getBoolean(key, false);
- }
-
- // Log a message if the value was non-null but not of the expected type
- private void typeWarning(String key, Object value, String className,
- Object defaultValue, ClassCastException e) {
- StringBuilder sb = new StringBuilder();
- sb.append("Key ");
- sb.append(key);
- sb.append(" expected ");
- sb.append(className);
- sb.append(" but value was a ");
- sb.append(value.getClass().getName());
- sb.append(". The default value ");
- sb.append(defaultValue);
- sb.append(" was returned.");
- Log.w(TAG, sb.toString());
- Log.w(TAG, "Attempt to cast generated internal exception:", e);
- }
-
- private void typeWarning(String key, Object value, String className,
- ClassCastException e) {
- typeWarning(key, value, className, "<null>", e);
+ return super.getBoolean(key);
}
/**
@@ -854,18 +765,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param defaultValue Value to return if key does not exist
* @return a boolean value
*/
+ @Override
public boolean getBoolean(String key, boolean defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Boolean) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Boolean", defaultValue, e);
- return defaultValue;
- }
+ return super.getBoolean(key, defaultValue);
}
/**
@@ -875,9 +777,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String
* @return a byte value
*/
+ @Override
public byte getByte(String key) {
- unparcel();
- return getByte(key, (byte) 0);
+ return super.getByte(key);
}
/**
@@ -888,18 +790,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param defaultValue Value to return if key does not exist
* @return a byte value
*/
+ @Override
public Byte getByte(String key, byte defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Byte) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Byte", defaultValue, e);
- return defaultValue;
- }
+ return super.getByte(key, defaultValue);
}
/**
@@ -909,9 +802,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String
* @return a char value
*/
+ @Override
public char getChar(String key) {
- unparcel();
- return getChar(key, (char) 0);
+ return super.getChar(key);
}
/**
@@ -922,18 +815,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param defaultValue Value to return if key does not exist
* @return a char value
*/
+ @Override
public char getChar(String key, char defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Character) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Character", defaultValue, e);
- return defaultValue;
- }
+ return super.getChar(key, defaultValue);
}
/**
@@ -943,9 +827,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String
* @return a short value
*/
+ @Override
public short getShort(String key) {
- unparcel();
- return getShort(key, (short) 0);
+ return super.getShort(key);
}
/**
@@ -956,18 +840,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param defaultValue Value to return if key does not exist
* @return a short value
*/
+ @Override
public short getShort(String key, short defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Short) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Short", defaultValue, e);
- return defaultValue;
- }
+ return super.getShort(key, defaultValue);
}
/**
@@ -977,9 +852,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String
* @return an int value
*/
+ @Override
public int getInt(String key) {
- unparcel();
- return getInt(key, 0);
+ return super.getInt(key);
}
/**
@@ -990,18 +865,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param defaultValue Value to return if key does not exist
* @return an int value
*/
+ @Override
public int getInt(String key, int defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Integer) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Integer", defaultValue, e);
- return defaultValue;
- }
+ return super.getInt(key, defaultValue);
}
/**
@@ -1011,9 +877,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String
* @return a long value
*/
+ @Override
public long getLong(String key) {
- unparcel();
- return getLong(key, 0L);
+ return super.getLong(key);
}
/**
@@ -1024,18 +890,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param defaultValue Value to return if key does not exist
* @return a long value
*/
+ @Override
public long getLong(String key, long defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Long) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Long", defaultValue, e);
- return defaultValue;
- }
+ return super.getLong(key, defaultValue);
}
/**
@@ -1045,9 +902,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String
* @return a float value
*/
+ @Override
public float getFloat(String key) {
- unparcel();
- return getFloat(key, 0.0f);
+ return super.getFloat(key);
}
/**
@@ -1058,18 +915,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param defaultValue Value to return if key does not exist
* @return a float value
*/
+ @Override
public float getFloat(String key, float defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Float) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Float", defaultValue, e);
- return defaultValue;
- }
+ return super.getFloat(key, defaultValue);
}
/**
@@ -1079,9 +927,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String
* @return a double value
*/
+ @Override
public double getDouble(String key) {
- unparcel();
- return getDouble(key, 0.0);
+ return super.getDouble(key);
}
/**
@@ -1092,18 +940,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param defaultValue Value to return if key does not exist
* @return a double value
*/
+ @Override
public double getDouble(String key, double defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (Double) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Double", defaultValue, e);
- return defaultValue;
- }
+ return super.getDouble(key, defaultValue);
}
/**
@@ -1114,15 +953,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a String value, or null
*/
+ @Override
public String getString(String key) {
- unparcel();
- final Object o = mMap.get(key);
- try {
- return (String) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "String", e);
- return null;
- }
+ return super.getString(key);
}
/**
@@ -1134,9 +967,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @return the String value associated with the given key, or defaultValue
* if no valid String object is currently mapped to that key.
*/
+ @Override
public String getString(String key, String defaultValue) {
- final String s = getString(key);
- return (s == null) ? defaultValue : s;
+ return super.getString(key, defaultValue);
}
/**
@@ -1147,15 +980,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a CharSequence value, or null
*/
+ @Override
public CharSequence getCharSequence(String key) {
- unparcel();
- final Object o = mMap.get(key);
- try {
- return (CharSequence) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "CharSequence", e);
- return null;
- }
+ return super.getCharSequence(key);
}
/**
@@ -1167,9 +994,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @return the CharSequence value associated with the given key, or defaultValue
* if no valid CharSequence object is currently mapped to that key.
*/
+ @Override
public CharSequence getCharSequence(String key, CharSequence defaultValue) {
- final CharSequence cs = getCharSequence(key);
- return (cs == null) ? defaultValue : cs;
+ return super.getCharSequence(key, defaultValue);
}
/**
@@ -1200,6 +1027,18 @@ public final class Bundle implements Parcelable, Cloneable {
* value is explicitly associated with the key.
*
* @param key a String, or null
+ * @return a PersistableBundle value, or null
+ */
+ public PersistableBundle getPersistableBundle(String key) {
+ return super.getPersistableBundle(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
* @return a Parcelable value, or null
*/
public <T extends Parcelable> T getParcelable(String key) {
@@ -1291,18 +1130,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a Serializable value, or null
*/
+ @Override
public Serializable getSerializable(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (Serializable) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Serializable", e);
- return null;
- }
+ return super.getSerializable(key);
}
/**
@@ -1313,18 +1143,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return an ArrayList<String> value, or null
*/
+ @Override
public ArrayList<Integer> getIntegerArrayList(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (ArrayList<Integer>) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "ArrayList<Integer>", e);
- return null;
- }
+ return super.getIntegerArrayList(key);
}
/**
@@ -1335,18 +1156,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return an ArrayList<String> value, or null
*/
+ @Override
public ArrayList<String> getStringArrayList(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (ArrayList<String>) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "ArrayList<String>", e);
- return null;
- }
+ return super.getStringArrayList(key);
}
/**
@@ -1357,18 +1169,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return an ArrayList<CharSequence> value, or null
*/
+ @Override
public ArrayList<CharSequence> getCharSequenceArrayList(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (ArrayList<CharSequence>) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "ArrayList<CharSequence>", e);
- return null;
- }
+ return super.getCharSequenceArrayList(key);
}
/**
@@ -1379,18 +1182,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a boolean[] value, or null
*/
+ @Override
public boolean[] getBooleanArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (boolean[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "byte[]", e);
- return null;
- }
+ return super.getBooleanArray(key);
}
/**
@@ -1401,18 +1195,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a byte[] value, or null
*/
+ @Override
public byte[] getByteArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (byte[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "byte[]", e);
- return null;
- }
+ return super.getByteArray(key);
}
/**
@@ -1423,18 +1208,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a short[] value, or null
*/
+ @Override
public short[] getShortArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (short[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "short[]", e);
- return null;
- }
+ return super.getShortArray(key);
}
/**
@@ -1445,18 +1221,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a char[] value, or null
*/
+ @Override
public char[] getCharArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (char[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "char[]", e);
- return null;
- }
+ return super.getCharArray(key);
}
/**
@@ -1467,18 +1234,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return an int[] value, or null
*/
+ @Override
public int[] getIntArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (int[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "int[]", e);
- return null;
- }
+ return super.getIntArray(key);
}
/**
@@ -1489,18 +1247,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a long[] value, or null
*/
+ @Override
public long[] getLongArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (long[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "long[]", e);
- return null;
- }
+ return super.getLongArray(key);
}
/**
@@ -1511,18 +1260,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a float[] value, or null
*/
+ @Override
public float[] getFloatArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (float[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "float[]", e);
- return null;
- }
+ return super.getFloatArray(key);
}
/**
@@ -1533,18 +1273,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a double[] value, or null
*/
+ @Override
public double[] getDoubleArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (double[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "double[]", e);
- return null;
- }
+ return super.getDoubleArray(key);
}
/**
@@ -1555,18 +1286,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a String[] value, or null
*/
+ @Override
public String[] getStringArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (String[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "String[]", e);
- return null;
- }
+ return super.getStringArray(key);
}
/**
@@ -1577,18 +1299,9 @@ public final class Bundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a CharSequence[] value, or null
*/
+ @Override
public CharSequence[] getCharSequenceArray(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (CharSequence[]) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "CharSequence[]", e);
- return null;
- }
+ return super.getCharSequenceArray(key);
}
/**
@@ -1641,10 +1354,12 @@ public final class Bundle implements Parcelable, Cloneable {
public static final Parcelable.Creator<Bundle> CREATOR =
new Parcelable.Creator<Bundle>() {
+ @Override
public Bundle createFromParcel(Parcel in) {
return in.readBundle();
}
+ @Override
public Bundle[] newArray(int size) {
return new Bundle[size];
}
@@ -1653,6 +1368,7 @@ public final class Bundle implements Parcelable, Cloneable {
/**
* Report the nature of this Parcelable's contents
*/
+ @Override
public int describeContents() {
int mask = 0;
if (hasFileDescriptors()) {
@@ -1660,44 +1376,17 @@ public final class Bundle implements Parcelable, Cloneable {
}
return mask;
}
-
+
/**
* Writes the Bundle contents to a Parcel, typically in order for
* it to be passed through an IBinder connection.
* @param parcel The parcel to copy this bundle to.
*/
+ @Override
public void writeToParcel(Parcel parcel, int flags) {
final boolean oldAllowFds = parcel.pushAllowFds(mAllowFds);
try {
- if (mParcelledData != null) {
- if (mParcelledData == EMPTY_PARCEL) {
- parcel.writeInt(0);
- } else {
- int length = mParcelledData.dataSize();
- parcel.writeInt(length);
- parcel.writeInt(BUNDLE_MAGIC);
- parcel.appendFrom(mParcelledData, 0, length);
- }
- } else {
- // Special case for empty bundles.
- if (mMap == null || mMap.size() <= 0) {
- parcel.writeInt(0);
- return;
- }
- int lengthPos = parcel.dataPosition();
- parcel.writeInt(-1); // dummy, will hold length
- parcel.writeInt(BUNDLE_MAGIC);
-
- int startPos = parcel.dataPosition();
- parcel.writeArrayMapInternal(mMap);
- int endPos = parcel.dataPosition();
-
- // Backpatch length
- parcel.setDataPosition(lengthPos);
- int length = endPos - startPos;
- parcel.writeInt(length);
- parcel.setDataPosition(endPos);
- }
+ super.writeToParcelInner(parcel, flags);
} finally {
parcel.restoreAllowFds(oldAllowFds);
}
@@ -1709,41 +1398,8 @@ public final class Bundle implements Parcelable, Cloneable {
* @param parcel The parcel to overwrite this bundle from.
*/
public void readFromParcel(Parcel parcel) {
- int length = parcel.readInt();
- if (length < 0) {
- throw new RuntimeException("Bad length in parcel: " + length);
- }
- readFromParcelInner(parcel, length);
- }
-
- void readFromParcelInner(Parcel parcel, int length) {
- if (length == 0) {
- // Empty Bundle or end of data.
- mParcelledData = EMPTY_PARCEL;
- mHasFds = false;
- mFdsKnown = true;
- return;
- }
- int magic = parcel.readInt();
- if (magic != BUNDLE_MAGIC) {
- //noinspection ThrowableInstanceNeverThrown
- throw new IllegalStateException("Bad magic number for Bundle: 0x"
- + Integer.toHexString(magic));
- }
-
- // Advance within this Parcel
- int offset = parcel.dataPosition();
- parcel.setDataPosition(offset + length);
-
- Parcel p = Parcel.obtain();
- p.setDataPosition(0);
- p.appendFrom(parcel, offset, length);
- if (DEBUG) Log.d(TAG, "Retrieving " + Integer.toHexString(System.identityHashCode(this))
- + ": " + length + " bundle bytes starting at " + offset);
- p.setDataPosition(0);
-
- mParcelledData = p;
- mHasFds = p.hasFileDescriptors();
+ super.readFromParcelInner(parcel);
+ mHasFds = mParcelledData.hasFileDescriptors();
mFdsKnown = true;
}
@@ -1759,4 +1415,5 @@ public final class Bundle implements Parcelable, Cloneable {
}
return "Bundle[" + mMap.toString() + "]";
}
+
}
diff --git a/core/java/android/os/CommonBundle.java b/core/java/android/os/CommonBundle.java
new file mode 100644
index 0000000..e11f170
--- /dev/null
+++ b/core/java/android/os/CommonBundle.java
@@ -0,0 +1,1384 @@
+/*
+ * 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.os;
+
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A mapping from String values to various types.
+ */
+abstract class CommonBundle implements Parcelable, Cloneable {
+ private static final String TAG = "Bundle";
+ static final boolean DEBUG = false;
+
+ static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L'
+ static final Parcel EMPTY_PARCEL;
+
+ static {
+ EMPTY_PARCEL = Parcel.obtain();
+ }
+
+ // Invariant - exactly one of mMap / mParcelledData will be null
+ // (except inside a call to unparcel)
+
+ ArrayMap<String, Object> mMap = null;
+
+ /*
+ * If mParcelledData is non-null, then mMap will be null and the
+ * data are stored as a Parcel containing a Bundle. When the data
+ * are unparcelled, mParcelledData willbe set to null.
+ */
+ Parcel mParcelledData = null;
+
+ /**
+ * The ClassLoader used when unparcelling data from mParcelledData.
+ */
+ private ClassLoader mClassLoader;
+
+ /**
+ * Constructs a new, empty Bundle that uses a specific ClassLoader for
+ * instantiating Parcelable and Serializable objects.
+ *
+ * @param loader An explicit ClassLoader to use when instantiating objects
+ * inside of the Bundle.
+ * @param capacity Initial size of the ArrayMap.
+ */
+ CommonBundle(ClassLoader loader, int capacity) {
+ mMap = capacity > 0 ?
+ new ArrayMap<String, Object>(capacity) : new ArrayMap<String, Object>();
+ mClassLoader = loader == null ? getClass().getClassLoader() : loader;
+ }
+
+ /**
+ * Constructs a new, empty Bundle.
+ */
+ CommonBundle() {
+ this((ClassLoader) null, 0);
+ }
+
+ /**
+ * Constructs a Bundle whose data is stored as a Parcel. The data
+ * will be unparcelled on first contact, using the assigned ClassLoader.
+ *
+ * @param parcelledData a Parcel containing a Bundle
+ */
+ CommonBundle(Parcel parcelledData) {
+ readFromParcelInner(parcelledData);
+ }
+
+ CommonBundle(Parcel parcelledData, int length) {
+ readFromParcelInner(parcelledData, length);
+ }
+
+ /**
+ * Constructs a new, empty Bundle that uses a specific ClassLoader for
+ * instantiating Parcelable and Serializable objects.
+ *
+ * @param loader An explicit ClassLoader to use when instantiating objects
+ * inside of the Bundle.
+ */
+ CommonBundle(ClassLoader loader) {
+ this(loader, 0);
+ }
+
+ /**
+ * Constructs a new, empty Bundle sized to hold the given number of
+ * elements. The Bundle will grow as needed.
+ *
+ * @param capacity the initial capacity of the Bundle
+ */
+ CommonBundle(int capacity) {
+ this((ClassLoader) null, capacity);
+ }
+
+ /**
+ * Constructs a Bundle containing a copy of the mappings from the given
+ * Bundle.
+ *
+ * @param b a Bundle to be copied.
+ */
+ CommonBundle(CommonBundle b) {
+ if (b.mParcelledData != null) {
+ if (b.mParcelledData == EMPTY_PARCEL) {
+ mParcelledData = EMPTY_PARCEL;
+ } else {
+ mParcelledData = Parcel.obtain();
+ mParcelledData.appendFrom(b.mParcelledData, 0, b.mParcelledData.dataSize());
+ mParcelledData.setDataPosition(0);
+ }
+ } else {
+ mParcelledData = null;
+ }
+
+ if (b.mMap != null) {
+ mMap = new ArrayMap<String, Object>(b.mMap);
+ } else {
+ mMap = null;
+ }
+
+ mClassLoader = b.mClassLoader;
+ }
+
+ /**
+ * TODO: optimize this later (getting just the value part of a Bundle
+ * with a single pair) once Bundle.forPair() above is implemented
+ * with a special single-value Map implementation/serialization.
+ *
+ * Note: value in single-pair Bundle may be null.
+ *
+ * @hide
+ */
+ String getPairValue() {
+ unparcel();
+ int size = mMap.size();
+ if (size > 1) {
+ Log.w(TAG, "getPairValue() used on Bundle with multiple pairs.");
+ }
+ if (size == 0) {
+ return null;
+ }
+ Object o = mMap.valueAt(0);
+ try {
+ return (String) o;
+ } catch (ClassCastException e) {
+ typeWarning("getPairValue()", o, "String", e);
+ return null;
+ }
+ }
+
+ /**
+ * Changes the ClassLoader this Bundle uses when instantiating objects.
+ *
+ * @param loader An explicit ClassLoader to use when instantiating objects
+ * inside of the Bundle.
+ */
+ void setClassLoader(ClassLoader loader) {
+ mClassLoader = loader;
+ }
+
+ /**
+ * Return the ClassLoader currently associated with this Bundle.
+ */
+ ClassLoader getClassLoader() {
+ return mClassLoader;
+ }
+
+ /**
+ * If the underlying data are stored as a Parcel, unparcel them
+ * using the currently assigned class loader.
+ */
+ /* package */ synchronized void unparcel() {
+ if (mParcelledData == null) {
+ if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ + ": no parcelled data");
+ return;
+ }
+
+ if (mParcelledData == EMPTY_PARCEL) {
+ if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ + ": empty");
+ if (mMap == null) {
+ mMap = new ArrayMap<String, Object>(1);
+ } else {
+ mMap.erase();
+ }
+ mParcelledData = null;
+ return;
+ }
+
+ int N = mParcelledData.readInt();
+ if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ + ": reading " + N + " maps");
+ if (N < 0) {
+ return;
+ }
+ if (mMap == null) {
+ mMap = new ArrayMap<String, Object>(N);
+ } else {
+ mMap.erase();
+ mMap.ensureCapacity(N);
+ }
+ mParcelledData.readArrayMapInternal(mMap, N, mClassLoader);
+ mParcelledData.recycle();
+ mParcelledData = null;
+ if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ + " final map: " + mMap);
+ }
+
+ /**
+ * @hide
+ */
+ boolean isParcelled() {
+ return mParcelledData != null;
+ }
+
+ /**
+ * Returns the number of mappings contained in this Bundle.
+ *
+ * @return the number of mappings as an int.
+ */
+ int size() {
+ unparcel();
+ return mMap.size();
+ }
+
+ /**
+ * Returns true if the mapping of this Bundle is empty, false otherwise.
+ */
+ boolean isEmpty() {
+ unparcel();
+ return mMap.isEmpty();
+ }
+
+ /**
+ * Removes all elements from the mapping of this Bundle.
+ */
+ void clear() {
+ unparcel();
+ mMap.clear();
+ }
+
+ /**
+ * Returns true if the given key is contained in the mapping
+ * of this Bundle.
+ *
+ * @param key a String key
+ * @return true if the key is part of the mapping, false otherwise
+ */
+ boolean containsKey(String key) {
+ unparcel();
+ return mMap.containsKey(key);
+ }
+
+ /**
+ * Returns the entry with the given key as an object.
+ *
+ * @param key a String key
+ * @return an Object, or null
+ */
+ Object get(String key) {
+ unparcel();
+ return mMap.get(key);
+ }
+
+ /**
+ * Removes any entry with the given key from the mapping of this Bundle.
+ *
+ * @param key a String key
+ */
+ void remove(String key) {
+ unparcel();
+ mMap.remove(key);
+ }
+
+ /**
+ * Inserts all mappings from the given PersistableBundle into this CommonBundle.
+ *
+ * @param bundle a PersistableBundle
+ */
+ void putAll(PersistableBundle bundle) {
+ unparcel();
+ bundle.unparcel();
+ mMap.putAll(bundle.mMap);
+ }
+
+ /**
+ * Returns a Set containing the Strings used as keys in this Bundle.
+ *
+ * @return a Set of String keys
+ */
+ Set<String> keySet() {
+ unparcel();
+ return mMap.keySet();
+ }
+
+ /**
+ * Inserts a Boolean value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a Boolean, or null
+ */
+ void putBoolean(String key, boolean value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a byte value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a byte
+ */
+ void putByte(String key, byte value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a char value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a char, or null
+ */
+ void putChar(String key, char value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a short value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a short
+ */
+ void putShort(String key, short value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts an int value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value an int, or null
+ */
+ void putInt(String key, int value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a long value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a long
+ */
+ void putLong(String key, long value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a float value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a float
+ */
+ void putFloat(String key, float value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a double value into the mapping of this Bundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a double
+ */
+ void putDouble(String key, double value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a String value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a String, or null
+ */
+ void putString(String key, String value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a CharSequence value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a CharSequence, or null
+ */
+ void putCharSequence(String key, CharSequence value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts an ArrayList<Integer> value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value an ArrayList<Integer> object, or null
+ */
+ void putIntegerArrayList(String key, ArrayList<Integer> value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts an ArrayList<String> value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value an ArrayList<String> object, or null
+ */
+ void putStringArrayList(String key, ArrayList<String> value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts an ArrayList<CharSequence> value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value an ArrayList<CharSequence> object, or null
+ */
+ void putCharSequenceArrayList(String key, ArrayList<CharSequence> value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a Serializable value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a Serializable object, or null
+ */
+ void putSerializable(String key, Serializable value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a boolean array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a boolean array object, or null
+ */
+ void putBooleanArray(String key, boolean[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a byte array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a byte array object, or null
+ */
+ void putByteArray(String key, byte[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a short array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a short array object, or null
+ */
+ void putShortArray(String key, short[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a char array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a char array object, or null
+ */
+ void putCharArray(String key, char[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts an int array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value an int array object, or null
+ */
+ void putIntArray(String key, int[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a long array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a long array object, or null
+ */
+ void putLongArray(String key, long[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a float array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a float array object, or null
+ */
+ void putFloatArray(String key, float[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a double array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a double array object, or null
+ */
+ void putDoubleArray(String key, double[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a String array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a String array object, or null
+ */
+ void putStringArray(String key, String[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a CharSequence array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a CharSequence array object, or null
+ */
+ void putCharSequenceArray(String key, CharSequence[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a Bundle object, or null
+ */
+ void putPersistableBundle(String key, PersistableBundle value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
+ * Returns the value associated with the given key, or false if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a boolean value
+ */
+ boolean getBoolean(String key) {
+ unparcel();
+ if (DEBUG) Log.d(TAG, "Getting boolean in "
+ + Integer.toHexString(System.identityHashCode(this)));
+ return getBoolean(key, false);
+ }
+
+ // Log a message if the value was non-null but not of the expected type
+ void typeWarning(String key, Object value, String className,
+ Object defaultValue, ClassCastException e) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Key ");
+ sb.append(key);
+ sb.append(" expected ");
+ sb.append(className);
+ sb.append(" but value was a ");
+ sb.append(value.getClass().getName());
+ sb.append(". The default value ");
+ sb.append(defaultValue);
+ sb.append(" was returned.");
+ Log.w(TAG, sb.toString());
+ Log.w(TAG, "Attempt to cast generated internal exception:", e);
+ }
+
+ void typeWarning(String key, Object value, String className,
+ ClassCastException e) {
+ typeWarning(key, value, className, "<null>", e);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a boolean value
+ */
+ boolean getBoolean(String key, boolean defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Boolean) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Boolean", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or (byte) 0 if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a byte value
+ */
+ byte getByte(String key) {
+ unparcel();
+ return getByte(key, (byte) 0);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a byte value
+ */
+ Byte getByte(String key, byte defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Byte) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Byte", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or (char) 0 if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a char value
+ */
+ char getChar(String key) {
+ unparcel();
+ return getChar(key, (char) 0);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a char value
+ */
+ char getChar(String key, char defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Character) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Character", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or (short) 0 if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a short value
+ */
+ short getShort(String key) {
+ unparcel();
+ return getShort(key, (short) 0);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a short value
+ */
+ short getShort(String key, short defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Short) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Short", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0 if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return an int value
+ */
+ int getInt(String key) {
+ unparcel();
+ return getInt(key, 0);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return an int value
+ */
+ int getInt(String key, int defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Integer) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Integer", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0L if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a long value
+ */
+ long getLong(String key) {
+ unparcel();
+ return getLong(key, 0L);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a long value
+ */
+ long getLong(String key, long defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Long) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Long", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0.0f if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a float value
+ */
+ float getFloat(String key) {
+ unparcel();
+ return getFloat(key, 0.0f);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a float value
+ */
+ float getFloat(String key, float defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Float) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Float", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0.0 if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a double value
+ */
+ double getDouble(String key) {
+ unparcel();
+ return getDouble(key, 0.0);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a double value
+ */
+ double getDouble(String key, double defaultValue) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return defaultValue;
+ }
+ try {
+ return (Double) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Double", defaultValue, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a String value, or null
+ */
+ String getString(String key) {
+ unparcel();
+ final Object o = mMap.get(key);
+ try {
+ return (String) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "String", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String, or null
+ * @param defaultValue Value to return if key does not exist
+ * @return the String value associated with the given key, or defaultValue
+ * if no valid String object is currently mapped to that key.
+ */
+ String getString(String key, String defaultValue) {
+ final String s = getString(key);
+ return (s == null) ? defaultValue : s;
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a CharSequence value, or null
+ */
+ CharSequence getCharSequence(String key) {
+ unparcel();
+ final Object o = mMap.get(key);
+ try {
+ return (CharSequence) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "CharSequence", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String, or null
+ * @param defaultValue Value to return if key does not exist
+ * @return the CharSequence value associated with the given key, or defaultValue
+ * if no valid CharSequence object is currently mapped to that key.
+ */
+ CharSequence getCharSequence(String key, CharSequence defaultValue) {
+ final CharSequence cs = getCharSequence(key);
+ return (cs == null) ? defaultValue : cs;
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a Bundle value, or null
+ */
+ PersistableBundle getPersistableBundle(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (PersistableBundle) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Bundle", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a Serializable value, or null
+ */
+ Serializable getSerializable(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (Serializable) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Serializable", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return an ArrayList<String> value, or null
+ */
+ ArrayList<Integer> getIntegerArrayList(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (ArrayList<Integer>) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "ArrayList<Integer>", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return an ArrayList<String> value, or null
+ */
+ ArrayList<String> getStringArrayList(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (ArrayList<String>) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "ArrayList<String>", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return an ArrayList<CharSequence> value, or null
+ */
+ ArrayList<CharSequence> getCharSequenceArrayList(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (ArrayList<CharSequence>) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "ArrayList<CharSequence>", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a boolean[] value, or null
+ */
+ boolean[] getBooleanArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (boolean[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "byte[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a byte[] value, or null
+ */
+ byte[] getByteArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (byte[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "byte[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a short[] value, or null
+ */
+ short[] getShortArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (short[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "short[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a char[] value, or null
+ */
+ char[] getCharArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (char[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "char[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return an int[] value, or null
+ */
+ int[] getIntArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (int[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "int[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a long[] value, or null
+ */
+ long[] getLongArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (long[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "long[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a float[] value, or null
+ */
+ float[] getFloatArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (float[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "float[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a double[] value, or null
+ */
+ double[] getDoubleArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (double[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "double[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a String[] value, or null
+ */
+ String[] getStringArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (String[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "String[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a CharSequence[] value, or null
+ */
+ CharSequence[] getCharSequenceArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (CharSequence[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "CharSequence[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Writes the Bundle contents to a Parcel, typically in order for
+ * it to be passed through an IBinder connection.
+ * @param parcel The parcel to copy this bundle to.
+ */
+ void writeToParcelInner(Parcel parcel, int flags) {
+ if (mParcelledData != null) {
+ if (mParcelledData == EMPTY_PARCEL) {
+ parcel.writeInt(0);
+ } else {
+ int length = mParcelledData.dataSize();
+ parcel.writeInt(length);
+ parcel.writeInt(BUNDLE_MAGIC);
+ parcel.appendFrom(mParcelledData, 0, length);
+ }
+ } else {
+ // Special case for empty bundles.
+ if (mMap == null || mMap.size() <= 0) {
+ parcel.writeInt(0);
+ return;
+ }
+ int lengthPos = parcel.dataPosition();
+ parcel.writeInt(-1); // dummy, will hold length
+ parcel.writeInt(BUNDLE_MAGIC);
+
+ int startPos = parcel.dataPosition();
+ parcel.writeArrayMapInternal(mMap);
+ int endPos = parcel.dataPosition();
+
+ // Backpatch length
+ parcel.setDataPosition(lengthPos);
+ int length = endPos - startPos;
+ parcel.writeInt(length);
+ parcel.setDataPosition(endPos);
+ }
+ }
+
+ /**
+ * Reads the Parcel contents into this Bundle, typically in order for
+ * it to be passed through an IBinder connection.
+ * @param parcel The parcel to overwrite this bundle from.
+ */
+ void readFromParcelInner(Parcel parcel) {
+ int length = parcel.readInt();
+ if (length < 0) {
+ throw new RuntimeException("Bad length in parcel: " + length);
+ }
+ readFromParcelInner(parcel, length);
+ }
+
+ private void readFromParcelInner(Parcel parcel, int length) {
+ if (length == 0) {
+ // Empty Bundle or end of data.
+ mParcelledData = EMPTY_PARCEL;
+ return;
+ }
+ int magic = parcel.readInt();
+ if (magic != BUNDLE_MAGIC) {
+ //noinspection ThrowableInstanceNeverThrown
+ throw new IllegalStateException("Bad magic number for Bundle: 0x"
+ + Integer.toHexString(magic));
+ }
+
+ // Advance within this Parcel
+ int offset = parcel.dataPosition();
+ parcel.setDataPosition(offset + length);
+
+ Parcel p = Parcel.obtain();
+ p.setDataPosition(0);
+ p.appendFrom(parcel, offset, length);
+ if (DEBUG) Log.d(TAG, "Retrieving " + Integer.toHexString(System.identityHashCode(this))
+ + ": " + length + " bundle bytes starting at " + offset);
+ p.setDataPosition(0);
+
+ mParcelledData = p;
+ }
+}
diff --git a/core/java/android/os/CommonClock.java b/core/java/android/os/CommonClock.java
index 7f41c5d..f83a90b 100644
--- a/core/java/android/os/CommonClock.java
+++ b/core/java/android/os/CommonClock.java
@@ -15,16 +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 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 7bf7367..e98a26b 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;
@@ -70,33 +69,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();
}
@@ -105,10 +77,6 @@ public class Environment {
public static void initForCurrentUser() {
final int userId = UserHandle.myUserId();
sCurrentUser = new UserEnvironment(userId);
-
- synchronized (sLock) {
- sPrimaryVolume = null;
- }
}
/** {@hide} */
@@ -241,7 +209,8 @@ 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;
@@ -626,28 +595,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";
@@ -655,7 +624,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";
@@ -663,7 +632,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";
@@ -671,7 +640,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";
@@ -679,14 +648,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";
@@ -694,7 +663,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";
@@ -710,7 +679,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);
}
/**
@@ -723,59 +700,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) {
@@ -838,6 +837,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 3e0b54a..d71c3e6 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -22,8 +22,6 @@ import android.system.OsConstants;
import android.util.Log;
import android.util.Slog;
-import libcore.io.IoUtils;
-
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -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,16 +345,20 @@ 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;
}
/**
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..899a958 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -28,11 +28,14 @@ import android.graphics.Bitmap;
*/
interface IUserManager {
UserInfo createUser(in String name, int flags);
+ UserInfo createProfileForUser(in String name, int flags, int userHandle);
+ void setUserEnabled(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, boolean enabledOnly);
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..95cb9f3 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;
@@ -222,6 +223,7 @@ public final class Parcel {
private static final int VAL_SPARSEBOOLEANARRAY = 22;
private static final int VAL_BOOLEANARRAY = 23;
private static final int VAL_CHARSEQUENCEARRAY = 24;
+ private static final int VAL_PERSISTABLEBUNDLE = 25;
// The initial int32 in a Binder call's reply Parcel header:
private static final int EX_SECURITY = -1;
@@ -637,6 +639,19 @@ public final class Parcel {
}
/**
+ * Flatten a PersistableBundle into the parcel at the current dataPosition(),
+ * growing dataCapacity() if needed.
+ */
+ public final void writePersistableBundle(PersistableBundle val) {
+ if (val == null) {
+ writeInt(-1);
+ return;
+ }
+
+ val.writeToParcel(this, 0);
+ }
+
+ /**
* Flatten a List into the parcel at the current dataPosition(), growing
* dataCapacity() if needed. The List values are written using
* {@link #writeValue} and must follow the specification there.
@@ -1255,6 +1270,9 @@ public final class Parcel {
} else if (v instanceof Byte) {
writeInt(VAL_BYTE);
writeInt((Byte) v);
+ } else if (v instanceof PersistableBundle) {
+ writeInt(VAL_PERSISTABLEBUNDLE);
+ writePersistableBundle((PersistableBundle) v);
} else {
Class<?> clazz = v.getClass();
if (clazz.isArray() && clazz.getComponentType() == Object.class) {
@@ -1632,6 +1650,35 @@ public final class Parcel {
}
/**
+ * Read and return a new Bundle object from the parcel at the current
+ * dataPosition(). Returns null if the previously written Bundle object was
+ * null.
+ */
+ public final PersistableBundle readPersistableBundle() {
+ return readPersistableBundle(null);
+ }
+
+ /**
+ * Read and return a new Bundle object from the parcel at the current
+ * dataPosition(), using the given class loader to initialize the class
+ * loader of the Bundle for later retrieval of Parcelable objects.
+ * Returns null if the previously written Bundle object was null.
+ */
+ public final PersistableBundle readPersistableBundle(ClassLoader loader) {
+ int length = readInt();
+ if (length < 0) {
+ if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length);
+ return null;
+ }
+
+ final PersistableBundle bundle = new PersistableBundle(this, length);
+ if (loader != null) {
+ bundle.setClassLoader(loader);
+ }
+ return bundle;
+ }
+
+ /**
* Read and return a byte[] object from the parcel.
*/
public final byte[] createByteArray() {
@@ -2067,7 +2114,7 @@ public final class Parcel {
return readByte();
case VAL_SERIALIZABLE:
- return readSerializable();
+ return readSerializable(loader);
case VAL_PARCELABLEARRAY:
return readParcelableArray(loader);
@@ -2081,6 +2128,9 @@ public final class Parcel {
case VAL_BUNDLE:
return readBundle(loader); // loading will be deferred
+ case VAL_PERSISTABLEBUNDLE:
+ return readPersistableBundle(loader);
+
default:
int off = dataPosition() - 4;
throw new RuntimeException(
@@ -2204,6 +2254,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 +2269,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 +2301,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 59795da..c6b2151 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -879,6 +879,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);
@@ -904,6 +906,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/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
new file mode 100644
index 0000000..c2cd3be
--- /dev/null
+++ b/core/java/android/os/PersistableBundle.java
@@ -0,0 +1,555 @@
+/*
+ * 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.os;
+
+import android.util.ArrayMap;
+
+import java.util.Set;
+
+/**
+ * A mapping from String values to various types that can be saved to persistent and later
+ * restored.
+ *
+ */
+public final class PersistableBundle extends CommonBundle {
+ public static final PersistableBundle EMPTY;
+ static final Parcel EMPTY_PARCEL;
+
+ static {
+ EMPTY = new PersistableBundle();
+ EMPTY.mMap = ArrayMap.EMPTY;
+ EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL;
+ }
+
+ /**
+ * Constructs a new, empty PersistableBundle.
+ */
+ public PersistableBundle() {
+ super();
+ }
+
+ /**
+ * Constructs a PersistableBundle whose data is stored as a Parcel. The data
+ * will be unparcelled on first contact, using the assigned ClassLoader.
+ *
+ * @param parcelledData a Parcel containing a PersistableBundle
+ */
+ PersistableBundle(Parcel parcelledData) {
+ super(parcelledData);
+ }
+
+ /* package */ PersistableBundle(Parcel parcelledData, int length) {
+ super(parcelledData, length);
+ }
+
+ /**
+ * Constructs a new, empty PersistableBundle that uses a specific ClassLoader for
+ * instantiating Parcelable and Serializable objects.
+ *
+ * @param loader An explicit ClassLoader to use when instantiating objects
+ * inside of the PersistableBundle.
+ */
+ public PersistableBundle(ClassLoader loader) {
+ super(loader);
+ }
+
+ /**
+ * Constructs a new, empty PersistableBundle sized to hold the given number of
+ * elements. The PersistableBundle will grow as needed.
+ *
+ * @param capacity the initial capacity of the PersistableBundle
+ */
+ public PersistableBundle(int capacity) {
+ super(capacity);
+ }
+
+ /**
+ * Constructs a PersistableBundle containing a copy of the mappings from the given
+ * PersistableBundle.
+ *
+ * @param b a PersistableBundle to be copied.
+ */
+ public PersistableBundle(PersistableBundle b) {
+ super(b);
+ }
+
+ /**
+ * Make a PersistableBundle for a single key/value pair.
+ *
+ * @hide
+ */
+ public static PersistableBundle forPair(String key, String value) {
+ PersistableBundle b = new PersistableBundle(1);
+ b.putString(key, value);
+ return b;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public String getPairValue() {
+ return super.getPairValue();
+ }
+
+ /**
+ * Changes the ClassLoader this PersistableBundle uses when instantiating objects.
+ *
+ * @param loader An explicit ClassLoader to use when instantiating objects
+ * inside of the PersistableBundle.
+ */
+ @Override
+ public void setClassLoader(ClassLoader loader) {
+ super.setClassLoader(loader);
+ }
+
+ /**
+ * Return the ClassLoader currently associated with this PersistableBundle.
+ */
+ @Override
+ public ClassLoader getClassLoader() {
+ return super.getClassLoader();
+ }
+
+ /**
+ * Clones the current PersistableBundle. The internal map is cloned, but the keys and
+ * values to which it refers are copied by reference.
+ */
+ @Override
+ public Object clone() {
+ return new PersistableBundle(this);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean isParcelled() {
+ return super.isParcelled();
+ }
+
+ /**
+ * Returns the number of mappings contained in this PersistableBundle.
+ *
+ * @return the number of mappings as an int.
+ */
+ @Override
+ public int size() {
+ return super.size();
+ }
+
+ /**
+ * Returns true if the mapping of this PersistableBundle is empty, false otherwise.
+ */
+ @Override
+ public boolean isEmpty() {
+ return super.isEmpty();
+ }
+
+ /**
+ * Removes all elements from the mapping of this PersistableBundle.
+ */
+ @Override
+ public void clear() {
+ super.clear();
+ }
+
+ /**
+ * Returns true if the given key is contained in the mapping
+ * of this PersistableBundle.
+ *
+ * @param key a String key
+ * @return true if the key is part of the mapping, false otherwise
+ */
+ @Override
+ public boolean containsKey(String key) {
+ return super.containsKey(key);
+ }
+
+ /**
+ * Returns the entry with the given key as an object.
+ *
+ * @param key a String key
+ * @return an Object, or null
+ */
+ @Override
+ public Object get(String key) {
+ return super.get(key);
+ }
+
+ /**
+ * Removes any entry with the given key from the mapping of this PersistableBundle.
+ *
+ * @param key a String key
+ */
+ @Override
+ public void remove(String key) {
+ super.remove(key);
+ }
+
+ /**
+ * Inserts all mappings from the given PersistableBundle into this Bundle.
+ *
+ * @param bundle a PersistableBundle
+ */
+ public void putAll(PersistableBundle bundle) {
+ super.putAll(bundle);
+ }
+
+ /**
+ * Returns a Set containing the Strings used as keys in this PersistableBundle.
+ *
+ * @return a Set of String keys
+ */
+ @Override
+ public Set<String> keySet() {
+ return super.keySet();
+ }
+
+ /**
+ * Inserts an int value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value an int, or null
+ */
+ @Override
+ public void putInt(String key, int value) {
+ super.putInt(key, value);
+ }
+
+ /**
+ * Inserts a long value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a long
+ */
+ @Override
+ public void putLong(String key, long value) {
+ super.putLong(key, value);
+ }
+
+ /**
+ * Inserts a double value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key.
+ *
+ * @param key a String, or null
+ * @param value a double
+ */
+ @Override
+ public void putDouble(String key, double value) {
+ super.putDouble(key, value);
+ }
+
+ /**
+ * Inserts a String value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a String, or null
+ */
+ @Override
+ public void putString(String key, String value) {
+ super.putString(key, value);
+ }
+
+ /**
+ * Inserts an int array value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value an int array object, or null
+ */
+ @Override
+ public void putIntArray(String key, int[] value) {
+ super.putIntArray(key, value);
+ }
+
+ /**
+ * Inserts a long array value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a long array object, or null
+ */
+ @Override
+ public void putLongArray(String key, long[] value) {
+ super.putLongArray(key, value);
+ }
+
+ /**
+ * Inserts a double array value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a double array object, or null
+ */
+ @Override
+ public void putDoubleArray(String key, double[] value) {
+ super.putDoubleArray(key, value);
+ }
+
+ /**
+ * Inserts a String array value into the mapping of this PersistableBundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a String array object, or null
+ */
+ @Override
+ public void putStringArray(String key, String[] value) {
+ super.putStringArray(key, value);
+ }
+
+ /**
+ * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a Bundle object, or null
+ */
+ public void putPersistableBundle(String key, PersistableBundle value) {
+ super.putPersistableBundle(key, value);
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0 if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return an int value
+ */
+ @Override
+ public int getInt(String key) {
+ return super.getInt(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return an int value
+ */
+ @Override
+ public int getInt(String key, int defaultValue) {
+ return super.getInt(key, defaultValue);
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0L if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a long value
+ */
+ @Override
+ public long getLong(String key) {
+ return super.getLong(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a long value
+ */
+ @Override
+ public long getLong(String key, long defaultValue) {
+ return super.getLong(key, defaultValue);
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0.0 if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @return a double value
+ */
+ @Override
+ public double getDouble(String key) {
+ return super.getDouble(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String
+ * @param defaultValue Value to return if key does not exist
+ * @return a double value
+ */
+ @Override
+ public double getDouble(String key, double defaultValue) {
+ return super.getDouble(key, defaultValue);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a String value, or null
+ */
+ @Override
+ public String getString(String key) {
+ return super.getString(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or defaultValue if
+ * no mapping of the desired type exists for the given key.
+ *
+ * @param key a String, or null
+ * @param defaultValue Value to return if key does not exist
+ * @return the String value associated with the given key, or defaultValue
+ * if no valid String object is currently mapped to that key.
+ */
+ @Override
+ public String getString(String key, String defaultValue) {
+ return super.getString(key, defaultValue);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a Bundle value, or null
+ */
+ @Override
+ public PersistableBundle getPersistableBundle(String key) {
+ return super.getPersistableBundle(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return an int[] value, or null
+ */
+ @Override
+ public int[] getIntArray(String key) {
+ return super.getIntArray(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a long[] value, or null
+ */
+ @Override
+ public long[] getLongArray(String key) {
+ return super.getLongArray(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a double[] value, or null
+ */
+ @Override
+ public double[] getDoubleArray(String key) {
+ return super.getDoubleArray(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return a String[] value, or null
+ */
+ @Override
+ public String[] getStringArray(String key) {
+ return super.getStringArray(key);
+ }
+
+ public static final Parcelable.Creator<PersistableBundle> CREATOR =
+ new Parcelable.Creator<PersistableBundle>() {
+ @Override
+ public PersistableBundle createFromParcel(Parcel in) {
+ return in.readPersistableBundle();
+ }
+
+ @Override
+ public PersistableBundle[] newArray(int size) {
+ return new PersistableBundle[size];
+ }
+ };
+
+ /**
+ * Report the nature of this Parcelable's contents
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Writes the PersistableBundle contents to a Parcel, typically in order for
+ * it to be passed through an IBinder connection.
+ * @param parcel The parcel to copy this bundle to.
+ */
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ final boolean oldAllowFds = parcel.pushAllowFds(false);
+ try {
+ super.writeToParcelInner(parcel, flags);
+ } finally {
+ parcel.restoreAllowFds(oldAllowFds);
+ }
+ }
+
+ /**
+ * Reads the Parcel contents into this PersistableBundle, typically in order for
+ * it to be passed through an IBinder connection.
+ * @param parcel The parcel to overwrite this bundle from.
+ */
+ public void readFromParcel(Parcel parcel) {
+ super.readFromParcelInner(parcel);
+ }
+
+ @Override
+ synchronized public String toString() {
+ if (mParcelledData != null) {
+ if (mParcelledData == EMPTY_PARCEL) {
+ return "PersistableBundle[EMPTY_PARCEL]";
+ } else {
+ return "PersistableBundle[mParcelledData.dataSize=" +
+ mParcelledData.dataSize() + "]";
+ }
+ }
+ return "PersistableBundle[" + mMap.toString() + "]";
+ }
+
+}
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/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index f671ed9..cdde4c7 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -375,6 +375,7 @@ public class RecoverySystem {
final ConditionVariable condition = new ConditionVariable();
Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION");
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
context.sendOrderedBroadcastAsUser(intent, UserHandle.OWNER,
android.Manifest.permission.MASTER_CLEAR,
new BroadcastReceiver() {
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..1b2b798 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));
}
/**
@@ -329,7 +336,7 @@ public class UserManager {
/**
* @hide
* Sets the value of a specific restriction on a specific user.
- * Requires the {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * Requires the MANAGE_USERS permission.
* @param key the key of the restriction
* @param value the value for the restriction
* @param userHandle the user whose restriction is to be changed.
@@ -409,6 +416,43 @@ 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;
+ }
+ }
+
+ /**
+ * Sets the user as enabled, if such an user exists.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * Note that the default is true, it's only that managed profiles might not be enabled.
+ *
+ * @param userHandle the id of the profile to enable
+ * @hide
+ */
+ public void setUserEnabled(int userHandle) {
+ try {
+ mService.setUserEnabled(userHandle);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Could not enable the profile", e);
+ }
+ }
+
+ /**
* Return the number of users currently created on the device.
*/
public int getUserCount() {
@@ -432,9 +476,105 @@ public class UserManager {
}
/**
- * Returns information for all users on this device.
+ * Returns list of the profiles of userHandle including
+ * userHandle itself.
+ * Note that it this returns both enabled and not enabled profiles. See
+ * {@link #getUserProfiles()} if you need only the enabled ones.
+ *
* 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, false /* enabledOnly */);
+ } 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 = new ArrayList<UserInfo>();
+ try {
+ users = mService.getProfiles(UserHandle.myUserId(), true /* enabledOnly */);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get user list", re);
+ return null;
+ }
+ 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 +705,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/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
index 11d8878..ff16f6c 100644
--- a/core/java/android/preference/PreferenceFragment.java
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -329,6 +329,11 @@ public abstract class PreferenceFragment extends Fragment implements
if (preferenceScreen != null) {
preferenceScreen.bind(getListView());
}
+ onBindPreferences();
+ }
+
+ /** @hide */
+ protected void onBindPreferences() {
}
/** @hide */
@@ -337,6 +342,26 @@ public abstract class PreferenceFragment extends Fragment implements
return mList;
}
+ /** @hide */
+ public boolean hasListView() {
+ if (mList != null) {
+ return true;
+ }
+ View root = getView();
+ if (root == null) {
+ return false;
+ }
+ View rawListView = root.findViewById(android.R.id.list);
+ if (!(rawListView instanceof ListView)) {
+ return false;
+ }
+ mList = (ListView)rawListView;
+ if (mList == null) {
+ return false;
+ }
+ return true;
+ }
+
private void ensureList() {
if (mList != null) {
return;
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..381a5f0 100644
--- a/core/java/android/preference/PreferenceGroupAdapter.java
+++ b/core/java/android/preference/PreferenceGroupAdapter.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.preference.Preference.OnPreferenceChangeInternalListener;
import android.view.View;
@@ -45,8 +46,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 +92,9 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
}
};
+ private int mHighlightedPosition = -1;
+ private Drawable mHighlightedDrawable;
+
private static class PreferenceLayout implements Comparable<PreferenceLayout> {
private int resId;
private int widgetResId;
@@ -207,6 +214,20 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
return this.getItem(position).getId();
}
+ /**
+ * @hide
+ */
+ public void setHighlighted(int position) {
+ mHighlightedPosition = position;
+ }
+
+ /**
+ * @hide
+ */
+ public void setHighlightedDrawable(Drawable drawable) {
+ mHighlightedDrawable = drawable;
+ }
+
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 +238,12 @@ 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);
+ if (position == mHighlightedPosition && mHighlightedDrawable != null) {
+ result.setBackgroundDrawable(mHighlightedDrawable);
+ }
+ result.setTag(preference.getKey());
+ 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/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 6519f7e..b907375 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -57,6 +57,10 @@ import java.util.List;
* <p>
* To create a document provider, extend {@link DocumentsProvider}, which
* provides a foundational implementation of this contract.
+ * <p>
+ * All client apps must hold a valid URI permission grant to access documents,
+ * typically issued when a user makes a selection through
+ * {@link Intent#ACTION_OPEN_DOCUMENT} or {@link Intent#ACTION_CREATE_DOCUMENT}.
*
* @see DocumentsProvider
*/
@@ -69,6 +73,8 @@ public final class DocumentsContract {
// content://com.example/root/sdcard/search/?query=pony
// content://com.example/document/12/
// content://com.example/document/12/children/
+ // content://com.example/via/12/document/24/
+ // content://com.example/via/12/document/24/children/
private DocumentsContract() {
}
@@ -425,6 +431,14 @@ public final class DocumentsContract {
public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
/**
+ * Flag indicating that this root supports directory selection.
+ *
+ * @see #COLUMN_FLAGS
+ * @see DocumentsProvider#isChildDocument(String, String)
+ */
+ public static final int FLAG_SUPPORTS_DIR_SELECTION = 1 << 4;
+
+ /**
* Flag indicating that this root is currently empty. This may be used
* to hide the root when opening documents, but the root will still be
* shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
@@ -484,12 +498,15 @@ public final class DocumentsContract {
/** {@hide} */
public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
+ /** {@hide} */
+ public static final String EXTRA_URI = "uri";
private static final String PATH_ROOT = "root";
private static final String PATH_RECENT = "recent";
private static final String PATH_DOCUMENT = "document";
private static final String PATH_CHILDREN = "children";
private static final String PATH_SEARCH = "search";
+ private static final String PATH_VIA = "via";
private static final String PARAM_QUERY = "query";
private static final String PARAM_MANAGE = "manage";
@@ -532,6 +549,17 @@ public final class DocumentsContract {
}
/**
+ * Build URI representing access to descendant documents of the given
+ * {@link Document#COLUMN_DOCUMENT_ID}.
+ *
+ * @see #getViaDocumentId(Uri)
+ */
+ public static Uri buildViaUri(String authority, String documentId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
+ .appendPath(PATH_VIA).appendPath(documentId).build();
+ }
+
+ /**
* Build URI representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
* document provider. When queried, a provider will return a single row with
* columns defined by {@link Document}.
@@ -545,6 +573,41 @@ public final class DocumentsContract {
}
/**
+ * Build URI representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
+ * document provider. Instead of directly accessing the target document,
+ * gain access via another document. The target document must be a
+ * descendant (child, grandchild, etc) of the via document.
+ * <p>
+ * This is typically used to access documents under a user-selected
+ * directory, since it doesn't require the user to separately confirm each
+ * new document access.
+ *
+ * @param viaUri a related document (directory) that the caller is
+ * leveraging to gain access to the target document. The target
+ * document must be a descendant of this directory.
+ * @param documentId the target document, which the caller may not have
+ * direct access to.
+ * @see Intent#ACTION_PICK_DIRECTORY
+ * @see DocumentsProvider#isChildDocument(String, String)
+ * @see #buildDocumentUri(String, String)
+ */
+ public static Uri buildDocumentViaUri(Uri viaUri, String documentId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(viaUri.getAuthority()).appendPath(PATH_VIA)
+ .appendPath(getViaDocumentId(viaUri)).appendPath(PATH_DOCUMENT)
+ .appendPath(documentId).build();
+ }
+
+ /** {@hide} */
+ public static Uri buildDocumentMaybeViaUri(Uri baseUri, String documentId) {
+ if (isViaUri(baseUri)) {
+ return buildDocumentViaUri(baseUri, documentId);
+ } else {
+ return buildDocumentUri(baseUri.getAuthority(), documentId);
+ }
+ }
+
+ /**
* Build URI representing the children of the given directory in a document
* provider. When queried, a provider will return zero or more rows with
* columns defined by {@link Document}.
@@ -562,6 +625,32 @@ public final class DocumentsContract {
}
/**
+ * Build URI representing the children of the given directory in a document
+ * provider. Instead of directly accessing the target document, gain access
+ * via another document. The target document must be a descendant (child,
+ * grandchild, etc) of the via document.
+ * <p>
+ * This is typically used to access documents under a user-selected
+ * directory, since it doesn't require the user to separately confirm each
+ * new document access.
+ *
+ * @param viaUri a related document (directory) that the caller is
+ * leveraging to gain access to the target document. The target
+ * document must be a descendant of this directory.
+ * @param parentDocumentId the target document, which the caller may not
+ * have direct access to.
+ * @see Intent#ACTION_PICK_DIRECTORY
+ * @see DocumentsProvider#isChildDocument(String, String)
+ * @see #buildChildDocumentsUri(String, String)
+ */
+ public static Uri buildChildDocumentsViaUri(Uri viaUri, String parentDocumentId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(viaUri.getAuthority()).appendPath(PATH_VIA)
+ .appendPath(getViaDocumentId(viaUri)).appendPath(PATH_DOCUMENT)
+ .appendPath(parentDocumentId).appendPath(PATH_CHILDREN).build();
+ }
+
+ /**
* Build URI representing a search for matching documents under a specific
* root in a document provider. When queried, a provider will return zero or
* more rows with columns defined by {@link Document}.
@@ -580,21 +669,31 @@ public final class DocumentsContract {
/**
* Test if the given URI represents a {@link Document} backed by a
* {@link DocumentsProvider}.
+ *
+ * @see #buildDocumentUri(String, String)
+ * @see #buildDocumentViaUri(Uri, String)
*/
public static boolean isDocumentUri(Context context, Uri uri) {
final List<String> paths = uri.getPathSegments();
- if (paths.size() < 2) {
- return false;
- }
- if (!PATH_DOCUMENT.equals(paths.get(0))) {
- return false;
+ if (paths.size() >= 2
+ && (PATH_DOCUMENT.equals(paths.get(0)) || PATH_VIA.equals(paths.get(0)))) {
+ return isDocumentsProvider(context, uri.getAuthority());
}
+ return false;
+ }
+
+ /** {@hide} */
+ public static boolean isViaUri(Uri uri) {
+ final List<String> paths = uri.getPathSegments();
+ return (paths.size() >= 2 && PATH_VIA.equals(paths.get(0)));
+ }
+ private static boolean isDocumentsProvider(Context context, String authority) {
final Intent intent = new Intent(PROVIDER_INTERFACE);
final List<ResolveInfo> infos = context.getPackageManager()
.queryIntentContentProviders(intent, 0);
for (ResolveInfo info : infos) {
- if (uri.getAuthority().equals(info.providerInfo.authority)) {
+ if (authority.equals(info.providerInfo.authority)) {
return true;
}
}
@@ -606,27 +705,40 @@ public final class DocumentsContract {
*/
public static String getRootId(Uri rootUri) {
final List<String> paths = rootUri.getPathSegments();
- if (paths.size() < 2) {
- throw new IllegalArgumentException("Not a root: " + rootUri);
- }
- if (!PATH_ROOT.equals(paths.get(0))) {
- throw new IllegalArgumentException("Not a root: " + rootUri);
+ if (paths.size() >= 2 && PATH_ROOT.equals(paths.get(0))) {
+ return paths.get(1);
}
- return paths.get(1);
+ throw new IllegalArgumentException("Invalid URI: " + rootUri);
}
/**
* Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
+ *
+ * @see #isDocumentUri(Context, Uri)
*/
public static String getDocumentId(Uri documentUri) {
final List<String> paths = documentUri.getPathSegments();
- if (paths.size() < 2) {
- throw new IllegalArgumentException("Not a document: " + documentUri);
+ if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) {
+ return paths.get(1);
}
- if (!PATH_DOCUMENT.equals(paths.get(0))) {
- throw new IllegalArgumentException("Not a document: " + documentUri);
+ if (paths.size() >= 4 && PATH_VIA.equals(paths.get(0))
+ && PATH_DOCUMENT.equals(paths.get(2))) {
+ return paths.get(3);
}
- return paths.get(1);
+ throw new IllegalArgumentException("Invalid URI: " + documentUri);
+ }
+
+ /**
+ * Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
+ *
+ * @see #isViaUri(Uri)
+ */
+ public static String getViaDocumentId(Uri documentUri) {
+ final List<String> paths = documentUri.getPathSegments();
+ if (paths.size() >= 2 && PATH_VIA.equals(paths.get(0))) {
+ return paths.get(1);
+ }
+ throw new IllegalArgumentException("Invalid URI: " + documentUri);
}
/**
@@ -758,7 +870,6 @@ public final class DocumentsContract {
* @param mimeType MIME type of new document
* @param displayName name of new document
* @return newly created document, or {@code null} if failed
- * @hide
*/
public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
String mimeType, String displayName) {
@@ -778,13 +889,12 @@ public final class DocumentsContract {
public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
String mimeType, String displayName) throws RemoteException {
final Bundle in = new Bundle();
- in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
+ in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
in.putString(Document.COLUMN_MIME_TYPE, mimeType);
in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
- return buildDocumentUri(
- parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
+ return out.getParcelable(DocumentsContract.EXTRA_URI);
}
/**
@@ -811,7 +921,7 @@ public final class DocumentsContract {
public static void deleteDocument(ContentProviderClient client, Uri documentUri)
throws RemoteException {
final Bundle in = new Bundle();
- in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
+ in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
client.call(METHOD_DELETE_DOCUMENT, null, in);
}
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 49816f8..1a7a00f2 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -46,6 +46,7 @@ import android.util.Log;
import libcore.io.IoUtils;
import java.io.FileNotFoundException;
+import java.util.Objects;
/**
* Base class for a document provider. A document provider offers read and write
@@ -125,6 +126,8 @@ public abstract class DocumentsProvider extends ContentProvider {
private static final int MATCH_SEARCH = 4;
private static final int MATCH_DOCUMENT = 5;
private static final int MATCH_CHILDREN = 6;
+ private static final int MATCH_DOCUMENT_VIA = 7;
+ private static final int MATCH_CHILDREN_VIA = 8;
private String mAuthority;
@@ -144,6 +147,8 @@ public abstract class DocumentsProvider extends ContentProvider {
mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
+ mMatcher.addURI(mAuthority, "via/*/document/*", MATCH_DOCUMENT_VIA);
+ mMatcher.addURI(mAuthority, "via/*/document/*/children", MATCH_CHILDREN_VIA);
// Sanity check our setup
if (!info.exported) {
@@ -161,6 +166,35 @@ public abstract class DocumentsProvider extends ContentProvider {
}
/**
+ * Test if a document is descendant (child, grandchild, etc) from the given
+ * parent. Providers must override this to support directory selection. You
+ * should avoid making network requests to keep this request fast.
+ *
+ * @param parentDocumentId parent to verify against.
+ * @param documentId child to verify.
+ * @return if given document is a descendant of the given parent.
+ * @see DocumentsContract.Root#FLAG_SUPPORTS_DIR_SELECTION
+ */
+ public boolean isChildDocument(String parentDocumentId, String documentId) {
+ return false;
+ }
+
+ /** {@hide} */
+ private void enforceVia(Uri documentUri) {
+ if (DocumentsContract.isViaUri(documentUri)) {
+ final String parent = DocumentsContract.getViaDocumentId(documentUri);
+ final String child = DocumentsContract.getDocumentId(documentUri);
+ if (Objects.equals(parent, child)) {
+ return;
+ }
+ if (!isChildDocument(parent, child)) {
+ throw new SecurityException(
+ "Document " + child + " is not a descendant of " + parent);
+ }
+ }
+ }
+
+ /**
* Create a new document and return its newly generated
* {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new
* {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must
@@ -182,9 +216,10 @@ public abstract class DocumentsProvider extends ContentProvider {
/**
* Delete the requested document. Upon returning, any URI permission grants
- * for the requested document will be revoked. If additional documents were
- * deleted as a side effect of this call, such as documents inside a
- * directory, the implementor is responsible for revoking those permissions.
+ * for the given document will be revoked. If additional documents were
+ * deleted as a side effect of this call (such as documents inside a
+ * directory) the implementor is responsible for revoking those permissions
+ * using {@link #revokeDocumentPermission(String)}.
*
* @param documentId the document to delete.
*/
@@ -420,8 +455,12 @@ public abstract class DocumentsProvider extends ContentProvider {
return querySearchDocuments(
getRootId(uri), getSearchDocumentsQuery(uri), projection);
case MATCH_DOCUMENT:
+ case MATCH_DOCUMENT_VIA:
+ enforceVia(uri);
return queryDocument(getDocumentId(uri), projection);
case MATCH_CHILDREN:
+ case MATCH_CHILDREN_VIA:
+ enforceVia(uri);
if (DocumentsContract.isManageMode(uri)) {
return queryChildDocumentsForManage(
getDocumentId(uri), projection, sortOrder);
@@ -449,6 +488,8 @@ public abstract class DocumentsProvider extends ContentProvider {
case MATCH_ROOT:
return DocumentsContract.Root.MIME_TYPE_ITEM;
case MATCH_DOCUMENT:
+ case MATCH_DOCUMENT_VIA:
+ enforceVia(uri);
return getDocumentType(getDocumentId(uri));
default:
return null;
@@ -460,6 +501,49 @@ public abstract class DocumentsProvider extends ContentProvider {
}
/**
+ * Implementation is provided by the parent class. Can be overridden to
+ * provide additional functionality, but subclasses <em>must</em> always
+ * call the superclass. If the superclass returns {@code null}, the subclass
+ * may implement custom behavior.
+ * <p>
+ * This is typically used to resolve a "via" URI into a concrete document
+ * reference, issuing a narrower single-document URI permission grant along
+ * the way.
+ *
+ * @see DocumentsContract#buildDocumentViaUri(Uri, String)
+ */
+ @Override
+ public Uri canonicalize(Uri uri) {
+ final Context context = getContext();
+ switch (mMatcher.match(uri)) {
+ case MATCH_DOCUMENT_VIA:
+ enforceVia(uri);
+
+ final Uri narrowUri = DocumentsContract.buildDocumentUri(uri.getAuthority(),
+ DocumentsContract.getDocumentId(uri));
+
+ // Caller may only have prefix grant, so extend them a grant to
+ // the narrow Uri. Caller already holds read grant to get here,
+ // so check for any other modes we should extend.
+ int modeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION;
+ if (context.checkCallingOrSelfUriPermission(uri,
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+ == PackageManager.PERMISSION_GRANTED) {
+ modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+ }
+ if (context.checkCallingOrSelfUriPermission(uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
+ == PackageManager.PERMISSION_GRANTED) {
+ modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
+ }
+ context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags);
+ return narrowUri;
+ }
+ return null;
+ }
+
+ /**
* Implementation is provided by the parent class. Throws by default, and
* cannot be overriden.
*
@@ -496,54 +580,47 @@ public abstract class DocumentsProvider extends ContentProvider {
* provide additional functionality, but subclasses <em>must</em> always
* call the superclass. If the superclass returns {@code null}, the subclass
* may implement custom behavior.
- *
- * @see #openDocument(String, String, CancellationSignal)
- * @see #deleteDocument(String)
*/
@Override
public Bundle call(String method, String arg, Bundle extras) {
- final Context context = getContext();
-
if (!method.startsWith("android:")) {
- // Let non-platform methods pass through
+ // Ignore non-platform methods
return super.call(method, arg, extras);
}
- final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID);
- final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId);
+ final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
+ final String authority = documentUri.getAuthority();
+ final String documentId = DocumentsContract.getDocumentId(documentUri);
- // Require that caller can manage requested document
- final boolean callerHasManage =
- context.checkCallingOrSelfPermission(android.Manifest.permission.MANAGE_DOCUMENTS)
- == PackageManager.PERMISSION_GRANTED;
- enforceWritePermissionInner(documentUri);
+ if (!mAuthority.equals(authority)) {
+ throw new SecurityException(
+ "Requested authority " + authority + " doesn't match provider " + mAuthority);
+ }
+ enforceVia(documentUri);
final Bundle out = new Bundle();
try {
if (METHOD_CREATE_DOCUMENT.equals(method)) {
+ enforceWritePermissionInner(documentUri);
+
final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
final String newDocumentId = createDocument(documentId, mimeType, displayName);
- out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId);
-
- // Extend permission grant towards caller if needed
- if (!callerHasManage) {
- final Uri newDocumentUri = DocumentsContract.buildDocumentUri(
- mAuthority, newDocumentId);
- context.grantUriPermission(getCallingPackage(), newDocumentUri,
- Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
- }
+
+ // No need to issue new grants here, since caller either has
+ // manage permission or a prefix grant. We might generate a
+ // "via" style URI if that's how they called us.
+ final Uri newDocumentUri = DocumentsContract.buildDocumentMaybeViaUri(documentUri,
+ newDocumentId);
+ out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
} else if (METHOD_DELETE_DOCUMENT.equals(method)) {
+ enforceWritePermissionInner(documentUri);
deleteDocument(documentId);
// Document no longer exists, clean up any grants
- context.revokeUriPermission(documentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+ revokeDocumentPermission(documentId);
} else {
throw new UnsupportedOperationException("Method not supported " + method);
@@ -555,12 +632,25 @@ public abstract class DocumentsProvider extends ContentProvider {
}
/**
+ * Revoke any active permission grants for the given
+ * {@link Document#COLUMN_DOCUMENT_ID}, usually called when a document
+ * becomes invalid. Follows the same semantics as
+ * {@link Context#revokeUriPermission(Uri, int)}.
+ */
+ public final void revokeDocumentPermission(String documentId) {
+ final Context context = getContext();
+ context.revokeUriPermission(DocumentsContract.buildDocumentUri(mAuthority, documentId), ~0);
+ context.revokeUriPermission(DocumentsContract.buildViaUri(mAuthority, documentId), ~0);
+ }
+
+ /**
* Implementation is provided by the parent class. Cannot be overriden.
*
* @see #openDocument(String, String, CancellationSignal)
*/
@Override
public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ enforceVia(uri);
return openDocument(getDocumentId(uri), mode, null);
}
@@ -572,17 +662,47 @@ public abstract class DocumentsProvider extends ContentProvider {
@Override
public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
throws FileNotFoundException {
+ enforceVia(uri);
return openDocument(getDocumentId(uri), mode, signal);
}
/**
* Implementation is provided by the parent class. Cannot be overriden.
*
+ * @see #openDocument(String, String, CancellationSignal)
+ */
+ @Override
+ @SuppressWarnings("resource")
+ public final AssetFileDescriptor openAssetFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ enforceVia(uri);
+ final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null);
+ return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
+ }
+
+ /**
+ * Implementation is provided by the parent class. Cannot be overriden.
+ *
+ * @see #openDocument(String, String, CancellationSignal)
+ */
+ @Override
+ @SuppressWarnings("resource")
+ public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal)
+ throws FileNotFoundException {
+ enforceVia(uri);
+ final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal);
+ return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
+ }
+
+ /**
+ * Implementation is provided by the parent class. Cannot be overriden.
+ *
* @see #openDocumentThumbnail(String, Point, CancellationSignal)
*/
@Override
public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
throws FileNotFoundException {
+ enforceVia(uri);
if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
return openDocumentThumbnail(getDocumentId(uri), sizeHint, null);
@@ -600,6 +720,7 @@ public abstract class DocumentsProvider extends ContentProvider {
public final AssetFileDescriptor openTypedAssetFile(
Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
throws FileNotFoundException {
+ enforceVia(uri);
if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal);
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 457afcc..cfab1b3 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -173,6 +173,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.
@@ -1393,6 +1405,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;
@@ -1863,6 +1880,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..ab06230 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -723,6 +723,13 @@ public final class Settings {
= "android.settings.NOTIFICATION_LISTENER_SETTINGS";
/**
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CONDITION_PROVIDER_SETTINGS
+ = "android.settings.ACTION_CONDITION_PROVIDER_SETTINGS";
+
+ /**
* Activity Action: Show settings for video captioning.
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard
@@ -749,6 +756,19 @@ 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";
+
+ /** {@hide} */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String
+ ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
+
// End of Intent actions for Settings
/**
@@ -3303,6 +3323,12 @@ public final class Settings {
"input_method_selector_visibility";
/**
+ * The currently selected voice interaction service flattened ComponentName.
+ * @hide
+ */
+ public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
+
+ /**
* bluetooth HCI snoop log configuration
* @hide
*/
@@ -3352,21 +3378,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 +3441,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 +3502,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 +3788,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 +3821,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
*/
@@ -4380,6 +4528,11 @@ public final class Settings {
*/
public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
+ /**
+ * @hide
+ */
+ public static final String ENABLED_CONDITION_PROVIDERS = "enabled_condition_providers";
+
/** @hide */
public static final String BAR_SERVICE_COMPONENT = "bar_service_component";
@@ -4967,6 +5120,13 @@ public final class Settings {
public static final String NETWORK_PREFERENCE = "network_preference";
/**
+ * Which package name to use for network scoring. If null, or if the package is not a valid
+ * scorer app, external network scores will neither be requested nor accepted.
+ * @hide
+ */
+ public static final String NETWORK_SCORER_APP = "network_scorer_app";
+
+ /**
* If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment
* to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been
* exceeded.
@@ -5833,6 +5993,12 @@ public final class Settings {
public static final String SHOW_PROCESSES = "show_processes";
/**
+ * If 1 low power mode is enabled.
+ * @hide
+ */
+ public static final String LOW_POWER_MODE = "low_power";
+
+ /**
* If 1, the activity manager will aggressively finish activities and
* processes as soon as they are no longer needed. If 0, the normal
* extended lifetime is used.
@@ -5941,6 +6107,66 @@ 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";
+ }
+
+ /**
+ * Opaque value, changes when persisted zen mode configuration changes.
+ *
+ * @hide
+ */
+ public static final String ZEN_MODE_CONFIG_ETAG = "zen_mode_config_etag";
+
+ /**
+ * 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..62252be
--- /dev/null
+++ b/core/java/android/provider/TvContract.java
@@ -0,0 +1,596 @@
+/*
+ * 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.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.net.Uri;
+
+import java.util.List;
+
+/**
+ * <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";
+
+ private static final String PATH_CHANNEL = "channel";
+ private static final String PATH_PROGRAM = "program";
+ private static final String PATH_INPUT = "input";
+
+ /**
+ * An optional query, update or delete URI parameter that allows the caller to specify start
+ * time (in milliseconds since the epoch) to filter programs.
+ *
+ * @hide
+ */
+ public static final String PARAM_START_TIME = "start_time";
+
+ /**
+ * An optional query, update or delete URI parameter that allows the caller to specify end time
+ * (in milliseconds since the epoch) to filter programs.
+ *
+ * @hide
+ */
+ public static final String PARAM_END_TIME = "end_time";
+
+ /**
+ * A query, update or delete URI parameter that allows the caller to operate on all or
+ * browsable-only channels. If set to "true", the rows that contain non-browsable channels are
+ * not affected.
+ *
+ * @hide
+ */
+ public static final String PARAM_BROWSABLE_ONLY = "browable_only";
+
+ /**
+ * 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 all browsable channels from a given TV input.
+ *
+ * @param name {@link ComponentName} of the {@link android.tv.TvInputService} that implements
+ * the given TV input.
+ */
+ public static final Uri buildChannelsUriForInput(ComponentName name) {
+ return buildChannelsUriForInput(name, true);
+ }
+
+ /**
+ * Builds a URI that points to all or browsable-only channels from a given TV input.
+ *
+ * @param name {@link ComponentName} of the {@link android.tv.TvInputService} that implements
+ * the given TV input.
+ * @param browsableOnly If set to {@code true} the URI points to only browsable channels. If set
+ * to {@code false} the URI points to all channels regardless of whether they are
+ * browsable or not.
+ */
+ public static final Uri buildChannelsUriForInput(ComponentName name, boolean browsableOnly) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
+ .appendPath(PATH_INPUT).appendPath(name.getPackageName())
+ .appendPath(name.getClassName()).appendPath(PATH_CHANNEL)
+ .appendQueryParameter(PARAM_BROWSABLE_ONLY, String.valueOf(browsableOnly)).build();
+ }
+
+ /**
+ * 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 all programs on a given channel.
+ *
+ * @param channelUri The URI of the channel to return programs for.
+ */
+ public static final Uri buildProgramsUriForChannel(Uri channelUri) {
+ if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
+ throw new IllegalArgumentException("Not a channel: " + channelUri);
+ }
+ String channelId = String.valueOf(ContentUris.parseId(channelUri));
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
+ .appendPath(PATH_CHANNEL).appendPath(channelId).appendPath(PATH_PROGRAM).build();
+ }
+
+ /**
+ * Builds a URI that points to programs on a specific channel whose schedules overlap with the
+ * given time frame.
+ *
+ * @param channelUri The URI of the channel to return programs for.
+ * @param startTime The start time used to filter programs. The returned programs should have
+ * {@link Programs#END_TIME_UTC_MILLIS} that is greater than this time.
+ * @param endTime The end time used to filter programs. The returned programs should have
+ * {@link Programs#START_TIME_UTC_MILLIS} that is less than this time.
+ */
+ public static final Uri buildProgramsUriForChannel(Uri channelUri, long startTime,
+ long endTime) {
+ Uri uri = buildProgramsUriForChannel(channelUri);
+ return uri.buildUpon().appendQueryParameter(PARAM_START_TIME, String.valueOf(startTime))
+ .appendQueryParameter(PARAM_END_TIME, String.valueOf(endTime)).build();
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Extracts the {@link Channels#PACKAGE_NAME} from a given URI.
+ *
+ * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or
+ * {@link #buildChannelsUriForInput(ComponentName, boolean)}.
+ * @hide
+ */
+ public static final String getPackageName(Uri channelsUri) {
+ final List<String> paths = channelsUri.getPathSegments();
+ if (paths.size() < 4) {
+ throw new IllegalArgumentException("Not channels: " + channelsUri);
+ }
+ if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) {
+ throw new IllegalArgumentException("Not channels: " + channelsUri);
+ }
+ return paths.get(1);
+ }
+
+ /**
+ * Extracts the {@link Channels#SERVICE_NAME} from a given URI.
+ *
+ * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or
+ * {@link #buildChannelsUriForInput(ComponentName, boolean)}.
+ * @hide
+ */
+ public static final String getServiceName(Uri channelsUri) {
+ final List<String> paths = channelsUri.getPathSegments();
+ if (paths.size() < 4) {
+ throw new IllegalArgumentException("Not channels: " + channelsUri);
+ }
+ if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) {
+ throw new IllegalArgumentException("Not channels: " + channelsUri);
+ }
+ return paths.get(2);
+ }
+
+ /**
+ * Extracts the {@link Channels#_ID} from a given URI.
+ *
+ * @param programsUri A URI constructed by {@link #buildProgramsUriForChannel(Uri)} or
+ * {@link #buildProgramsUriForChannel(Uri, long, long)}.
+ * @hide
+ */
+ public static final String getChannelId(Uri programsUri) {
+ final List<String> paths = programsUri.getPathSegments();
+ if (paths.size() < 3) {
+ throw new IllegalArgumentException("Not programs: " + programsUri);
+ }
+ if (!PATH_CHANNEL.equals(paths.get(0)) || !PATH_PROGRAM.equals(paths.get(2))) {
+ throw new IllegalArgumentException("Not programs: " + programsUri);
+ }
+ return paths.get(1);
+ }
+
+
+ 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 + "/"
+ + PATH_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 + "/"
+ + PATH_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/Condition.aidl b/core/java/android/service/notification/Condition.aidl
new file mode 100644
index 0000000..432852c
--- /dev/null
+++ b/core/java/android/service/notification/Condition.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.service.notification;
+
+parcelable Condition;
+
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
new file mode 100644
index 0000000..aa724f0
--- /dev/null
+++ b/core/java/android/service/notification/Condition.java
@@ -0,0 +1,176 @@
+/**
+ * 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.notification;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Condition information from condition providers.
+ *
+ * @hide
+ */
+public class Condition implements Parcelable {
+
+ public static final String SCHEME = "condition";
+
+ public static final int STATE_FALSE = 0;
+ public static final int STATE_TRUE = 1;
+ public static final int STATE_UNKNOWN = 2;
+ public static final int STATE_ERROR = 3;
+
+ public static final int FLAG_RELEVANT_NOW = 1 << 0;
+ public static final int FLAG_RELEVANT_ALWAYS = 1 << 1;
+
+ public final Uri id;
+ public final String summary;
+ public final String line1;
+ public final String line2;
+ public final int icon;
+ public final int state;
+ public final int flags;
+
+ public Condition(Uri id, String summary, String line1, String line2, int icon,
+ int state, int flags) {
+ if (id == null) throw new IllegalArgumentException("id is required");
+ if (summary == null) throw new IllegalArgumentException("summary is required");
+ if (line1 == null) throw new IllegalArgumentException("line1 is required");
+ if (line2 == null) throw new IllegalArgumentException("line2 is required");
+ if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state);
+ this.id = id;
+ this.summary = summary;
+ this.line1 = line1;
+ this.line2 = line2;
+ this.icon = icon;
+ this.state = state;
+ this.flags = flags;
+ }
+
+ private Condition(Parcel source) {
+ this((Uri)source.readParcelable(Condition.class.getClassLoader()),
+ source.readString(),
+ source.readString(),
+ source.readString(),
+ source.readInt(),
+ source.readInt(),
+ source.readInt());
+ }
+
+ private static boolean isValidState(int state) {
+ return state >= STATE_FALSE && state <= STATE_ERROR;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(id, 0);
+ dest.writeString(summary);
+ dest.writeString(line1);
+ dest.writeString(line2);
+ dest.writeInt(icon);
+ dest.writeInt(state);
+ dest.writeInt(this.flags);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder(Condition.class.getSimpleName()).append('[')
+ .append("id=").append(id)
+ .append(",summary=").append(summary)
+ .append(",line1=").append(line1)
+ .append(",line2=").append(line2)
+ .append(",icon=").append(icon)
+ .append(",state=").append(stateToString(state))
+ .append(",flags=").append(flags)
+ .append(']').toString();
+ }
+
+ public static String stateToString(int state) {
+ if (state == STATE_FALSE) return "STATE_FALSE";
+ if (state == STATE_TRUE) return "STATE_TRUE";
+ if (state == STATE_UNKNOWN) return "STATE_UNKNOWN";
+ if (state == STATE_ERROR) return "STATE_ERROR";
+ throw new IllegalArgumentException("state is invalid: " + state);
+ }
+
+ public static String relevanceToString(int flags) {
+ final boolean now = (flags & FLAG_RELEVANT_NOW) != 0;
+ final boolean always = (flags & FLAG_RELEVANT_ALWAYS) != 0;
+ if (!now && !always) return "NONE";
+ if (now && always) return "NOW, ALWAYS";
+ return now ? "NOW" : "ALWAYS";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Condition)) return false;
+ if (o == this) return true;
+ final Condition other = (Condition) o;
+ return Objects.equals(other.id, id)
+ && Objects.equals(other.summary, summary)
+ && Objects.equals(other.line1, line1)
+ && Objects.equals(other.line2, line2)
+ && other.icon == icon
+ && other.state == state
+ && other.flags == flags;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, summary, line1, line2, icon, state, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public Condition copy() {
+ final Parcel parcel = Parcel.obtain();
+ try {
+ writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return new Condition(parcel);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ public static Uri.Builder newId(Context context) {
+ return new Uri.Builder().scheme(SCHEME).authority(context.getPackageName());
+ }
+
+ public static boolean isValidId(Uri id, String pkg) {
+ return id != null && id.getScheme().equals(SCHEME) && id.getAuthority().equals(pkg);
+ }
+
+ public static final Parcelable.Creator<Condition> CREATOR
+ = new Parcelable.Creator<Condition>() {
+ @Override
+ public Condition createFromParcel(Parcel source) {
+ return new Condition(source);
+ }
+
+ @Override
+ public Condition[] newArray(int size) {
+ return new Condition[size];
+ }
+ };
+}
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
new file mode 100644
index 0000000..326412f
--- /dev/null
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -0,0 +1,161 @@
+/*
+ * 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.notification;
+
+import android.annotation.SdkConstant;
+import android.app.INotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * A service that provides conditions about boolean state.
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * the {@link android.Manifest.permission#BIND_CONDITION_PROVIDER_SERVICE} permission
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
+ * <pre>
+ * &lt;service android:name=".MyConditionProvider"
+ * android:label="&#64;string/service_name"
+ * android:permission="android.permission.BIND_CONDITION_PROVIDER_SERVICE">
+ * &lt;intent-filter>
+ * &lt;action android:name="android.service.notification.ConditionProviderService" />
+ * &lt;/intent-filter>
+ * &lt;/service></pre>
+ *
+ * @hide
+ */
+public abstract class ConditionProviderService extends Service {
+ private final String TAG = ConditionProviderService.class.getSimpleName()
+ + "[" + getClass().getSimpleName() + "]";
+
+ private final H mHandler = new H();
+
+ private Provider mProvider;
+ private INotificationManager mNoMan;
+
+ /**
+ * 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.notification.ConditionProviderService";
+
+ abstract public void onConnected();
+ abstract public void onRequestConditions(int relevance);
+ abstract public void onSubscribe(Uri conditionId);
+ abstract public void onUnsubscribe(Uri conditionId);
+
+ private final INotificationManager getNotificationInterface() {
+ if (mNoMan == null) {
+ mNoMan = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ }
+ return mNoMan;
+ }
+
+ public final void notifyCondition(Condition condition) {
+ if (condition == null) return;
+ notifyConditions(new Condition[]{ condition });
+ }
+
+ public final void notifyConditions(Condition... conditions) {
+ if (!isBound() || conditions == null) return;
+ try {
+ getNotificationInterface().notifyConditions(getPackageName(), mProvider, conditions);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact notification manager", ex);
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (mProvider == null) {
+ mProvider = new Provider();
+ }
+ return mProvider;
+ }
+
+ private boolean isBound() {
+ if (mProvider == null) {
+ Log.w(TAG, "Condition provider service not yet bound.");
+ return false;
+ }
+ return true;
+ }
+
+ private final class Provider extends IConditionProvider.Stub {
+ @Override
+ public void onConnected() {
+ mHandler.obtainMessage(H.ON_CONNECTED).sendToTarget();
+ }
+
+ @Override
+ public void onRequestConditions(int relevance) {
+ mHandler.obtainMessage(H.ON_REQUEST_CONDITIONS, relevance, 0).sendToTarget();
+ }
+
+ @Override
+ public void onSubscribe(Uri conditionId) {
+ mHandler.obtainMessage(H.ON_SUBSCRIBE, conditionId).sendToTarget();
+ }
+
+ @Override
+ public void onUnsubscribe(Uri conditionId) {
+ mHandler.obtainMessage(H.ON_UNSUBSCRIBE, conditionId).sendToTarget();
+ }
+ }
+
+ private final class H extends Handler {
+ private static final int ON_CONNECTED = 1;
+ private static final int ON_REQUEST_CONDITIONS = 2;
+ private static final int ON_SUBSCRIBE = 3;
+ private static final int ON_UNSUBSCRIBE = 4;
+
+ @Override
+ public void handleMessage(Message msg) {
+ String name = null;
+ try {
+ switch(msg.what) {
+ case ON_CONNECTED:
+ name = "onConnected";
+ onConnected();
+ break;
+ case ON_REQUEST_CONDITIONS:
+ name = "onRequestConditions";
+ onRequestConditions(msg.arg1);
+ break;
+ case ON_SUBSCRIBE:
+ name = "onSubscribe";
+ onSubscribe((Uri)msg.obj);
+ break;
+ case ON_UNSUBSCRIBE:
+ name = "onUnsubscribe";
+ onUnsubscribe((Uri)msg.obj);
+ break;
+ }
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running " + name, t);
+ }
+ }
+ }
+}
diff --git a/core/java/android/service/notification/IConditionListener.aidl b/core/java/android/service/notification/IConditionListener.aidl
new file mode 100644
index 0000000..01f874f
--- /dev/null
+++ b/core/java/android/service/notification/IConditionListener.aidl
@@ -0,0 +1,25 @@
+/**
+ * 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.notification;
+
+import android.net.Uri;
+import android.service.notification.Condition;
+
+/** @hide */
+oneway interface IConditionListener {
+ void onConditionsReceived(in Condition[] conditions);
+} \ No newline at end of file
diff --git a/core/java/android/service/notification/IConditionProvider.aidl b/core/java/android/service/notification/IConditionProvider.aidl
new file mode 100644
index 0000000..ada8939
--- /dev/null
+++ b/core/java/android/service/notification/IConditionProvider.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.notification;
+
+import android.net.Uri;
+import android.service.notification.Condition;
+
+/** @hide */
+oneway interface IConditionProvider {
+ void onConnected();
+ void onRequestConditions(int relevance);
+ void onSubscribe(in Uri conditionId);
+ void onUnsubscribe(in Uri conditionId);
+} \ No newline at end of file
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..72720d1 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 + '|' + 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/notification/ZenModeConfig.aidl b/core/java/android/service/notification/ZenModeConfig.aidl
new file mode 100644
index 0000000..c73b75e
--- /dev/null
+++ b/core/java/android/service/notification/ZenModeConfig.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.service.notification;
+
+parcelable ZenModeConfig;
+
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
new file mode 100644
index 0000000..846e292
--- /dev/null
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -0,0 +1,312 @@
+/**
+ * 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.notification;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Persisted configuration for zen mode.
+ *
+ * @hide
+ */
+public class ZenModeConfig implements Parcelable {
+
+ public static final String SLEEP_MODE_NIGHTS = "nights";
+ public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights";
+
+ private static final int XML_VERSION = 1;
+ private static final String ZEN_TAG = "zen";
+ private static final String ZEN_ATT_VERSION = "version";
+ private static final String ALLOW_TAG = "allow";
+ private static final String ALLOW_ATT_CALLS = "calls";
+ private static final String ALLOW_ATT_MESSAGES = "messages";
+ private static final String SLEEP_TAG = "sleep";
+ private static final String SLEEP_ATT_MODE = "mode";
+
+ private static final String SLEEP_ATT_START_HR = "startHour";
+ private static final String SLEEP_ATT_START_MIN = "startMin";
+ private static final String SLEEP_ATT_END_HR = "endHour";
+ private static final String SLEEP_ATT_END_MIN = "endMin";
+
+ private static final String CONDITION_TAG = "condition";
+ private static final String CONDITION_ATT_COMPONENT = "component";
+ private static final String CONDITION_ATT_ID = "id";
+
+ public boolean allowCalls;
+ public boolean allowMessages;
+
+ public String sleepMode;
+ public int sleepStartHour;
+ public int sleepStartMinute;
+ public int sleepEndHour;
+ public int sleepEndMinute;
+ public ComponentName[] conditionComponents;
+ public Uri[] conditionIds;
+
+ public ZenModeConfig() { }
+
+ public ZenModeConfig(Parcel source) {
+ allowCalls = source.readInt() == 1;
+ allowMessages = source.readInt() == 1;
+ if (source.readInt() == 1) {
+ sleepMode = source.readString();
+ }
+ sleepStartHour = source.readInt();
+ sleepStartMinute = source.readInt();
+ sleepEndHour = source.readInt();
+ sleepEndMinute = source.readInt();
+ int len = source.readInt();
+ if (len > 0) {
+ conditionComponents = new ComponentName[len];
+ source.readTypedArray(conditionComponents, ComponentName.CREATOR);
+ }
+ len = source.readInt();
+ if (len > 0) {
+ conditionIds = new Uri[len];
+ source.readTypedArray(conditionIds, Uri.CREATOR);
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(allowCalls ? 1 : 0);
+ dest.writeInt(allowMessages ? 1 : 0);
+ if (sleepMode != null) {
+ dest.writeInt(1);
+ dest.writeString(sleepMode);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(sleepStartHour);
+ dest.writeInt(sleepStartMinute);
+ dest.writeInt(sleepEndHour);
+ dest.writeInt(sleepEndMinute);
+ if (conditionComponents != null && conditionComponents.length > 0) {
+ dest.writeInt(conditionComponents.length);
+ dest.writeTypedArray(conditionComponents, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ if (conditionIds != null && conditionIds.length > 0) {
+ dest.writeInt(conditionIds.length);
+ dest.writeTypedArray(conditionIds, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
+ .append("allowCalls=").append(allowCalls)
+ .append(",allowMessages=").append(allowMessages)
+ .append(",sleepMode=").append(sleepMode)
+ .append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute)
+ .append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute)
+ .append(",conditionComponents=")
+ .append(conditionComponents == null ? null : TextUtils.join(",", conditionComponents))
+ .append(",conditionIds=")
+ .append(conditionIds == null ? null : TextUtils.join(",", conditionIds))
+ .append(']').toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ZenModeConfig)) return false;
+ if (o == this) return true;
+ final ZenModeConfig other = (ZenModeConfig) o;
+ return other.allowCalls == allowCalls
+ && other.allowMessages == allowMessages
+ && Objects.equals(other.sleepMode, sleepMode)
+ && other.sleepStartHour == sleepStartHour
+ && other.sleepStartMinute == sleepStartMinute
+ && other.sleepEndHour == sleepEndHour
+ && other.sleepEndMinute == sleepEndMinute
+ && Objects.deepEquals(other.conditionComponents, conditionComponents)
+ && Objects.deepEquals(other.conditionIds, conditionIds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(allowCalls, allowMessages, sleepMode, sleepStartHour,
+ sleepStartMinute, sleepEndHour, sleepEndMinute,
+ Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds));
+ }
+
+ public boolean isValid() {
+ return isValidHour(sleepStartHour) && isValidMinute(sleepStartMinute)
+ && isValidHour(sleepEndHour) && isValidMinute(sleepEndMinute)
+ && (sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS)
+ || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS));
+ }
+
+ public static ZenModeConfig readXml(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int type = parser.getEventType();
+ if (type != XmlPullParser.START_TAG) return null;
+ String tag = parser.getName();
+ if (!ZEN_TAG.equals(tag)) return null;
+ final ZenModeConfig rt = new ZenModeConfig();
+ final int version = Integer.parseInt(parser.getAttributeValue(null, ZEN_ATT_VERSION));
+ final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>();
+ final ArrayList<Uri> conditionIds = new ArrayList<Uri>();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ tag = parser.getName();
+ if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
+ if (!conditionComponents.isEmpty()) {
+ rt.conditionComponents = conditionComponents
+ .toArray(new ComponentName[conditionComponents.size()]);
+ rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]);
+ }
+ return rt;
+ }
+ if (type == XmlPullParser.START_TAG) {
+ if (ALLOW_TAG.equals(tag)) {
+ rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
+ rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
+ } else if (SLEEP_TAG.equals(tag)) {
+ final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE);
+ rt.sleepMode = (SLEEP_MODE_NIGHTS.equals(mode)
+ || SLEEP_MODE_WEEKNIGHTS.equals(mode)) ? mode : null;
+ final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0);
+ final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0);
+ final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0);
+ final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0);
+ rt.sleepStartHour = isValidHour(startHour) ? startHour : 0;
+ rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0;
+ rt.sleepEndHour = isValidHour(endHour) ? endHour : 0;
+ rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0;
+ } else if (CONDITION_TAG.equals(tag)) {
+ final ComponentName component =
+ safeComponentName(parser, CONDITION_ATT_COMPONENT);
+ final Uri conditionId = safeUri(parser, CONDITION_ATT_ID);
+ if (component != null && conditionId != null) {
+ conditionComponents.add(component);
+ conditionIds.add(conditionId);
+ }
+ }
+ }
+ }
+ throw new IllegalStateException("Failed to reach END_DOCUMENT");
+ }
+
+ public void writeXml(XmlSerializer out) throws IOException {
+ out.startTag(null, ZEN_TAG);
+ out.attribute(null, ZEN_ATT_VERSION, Integer.toString(XML_VERSION));
+
+ out.startTag(null, ALLOW_TAG);
+ out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls));
+ out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages));
+ out.endTag(null, ALLOW_TAG);
+
+ out.startTag(null, SLEEP_TAG);
+ if (sleepMode != null) {
+ out.attribute(null, SLEEP_ATT_MODE, sleepMode);
+ }
+ out.attribute(null, SLEEP_ATT_START_HR, Integer.toString(sleepStartHour));
+ out.attribute(null, SLEEP_ATT_START_MIN, Integer.toString(sleepStartMinute));
+ out.attribute(null, SLEEP_ATT_END_HR, Integer.toString(sleepEndHour));
+ out.attribute(null, SLEEP_ATT_END_MIN, Integer.toString(sleepEndMinute));
+ out.endTag(null, SLEEP_TAG);
+
+ if (conditionComponents != null && conditionIds != null
+ && conditionComponents.length == conditionIds.length) {
+ for (int i = 0; i < conditionComponents.length; i++) {
+ out.startTag(null, CONDITION_TAG);
+ out.attribute(null, CONDITION_ATT_COMPONENT,
+ conditionComponents[i].flattenToString());
+ out.attribute(null, CONDITION_ATT_ID, conditionIds[i].toString());
+ out.endTag(null, CONDITION_TAG);
+ }
+ }
+ out.endTag(null, ZEN_TAG);
+ }
+
+ public static boolean isValidHour(int val) {
+ return val >= 0 && val < 24;
+ }
+
+ public static boolean isValidMinute(int val) {
+ return val >= 0 && val < 60;
+ }
+
+ private static boolean safeBoolean(XmlPullParser parser, String att, boolean defValue) {
+ final String val = parser.getAttributeValue(null, att);
+ if (TextUtils.isEmpty(val)) return defValue;
+ return Boolean.valueOf(val);
+ }
+
+ private static int safeInt(XmlPullParser parser, String att, int defValue) {
+ final String val = parser.getAttributeValue(null, att);
+ if (TextUtils.isEmpty(val)) return defValue;
+ return Integer.valueOf(val);
+ }
+
+ private static ComponentName safeComponentName(XmlPullParser parser, String att) {
+ final String val = parser.getAttributeValue(null, att);
+ if (TextUtils.isEmpty(val)) return null;
+ return ComponentName.unflattenFromString(val);
+ }
+
+ private static Uri safeUri(XmlPullParser parser, String att) {
+ final String val = parser.getAttributeValue(null, att);
+ if (TextUtils.isEmpty(val)) return null;
+ return Uri.parse(val);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public ZenModeConfig copy() {
+ final Parcel parcel = Parcel.obtain();
+ try {
+ writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return new ZenModeConfig(parcel);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ public static final Parcelable.Creator<ZenModeConfig> CREATOR
+ = new Parcelable.Creator<ZenModeConfig>() {
+ @Override
+ public ZenModeConfig createFromParcel(Parcel source) {
+ return new ZenModeConfig(source);
+ }
+
+ @Override
+ public ZenModeConfig[] newArray(int size) {
+ return new ZenModeConfig[size];
+ }
+ };
+}
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/voice/IVoiceInteractionService.aidl b/core/java/android/service/voice/IVoiceInteractionService.aidl
new file mode 100644
index 0000000..e9e2f4c
--- /dev/null
+++ b/core/java/android/service/voice/IVoiceInteractionService.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.voice;
+
+/**
+ * @hide
+ */
+oneway interface IVoiceInteractionService {
+}
diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl
new file mode 100644
index 0000000..7dbf66b
--- /dev/null
+++ b/core/java/android/service/voice/IVoiceInteractionSession.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.voice;
+
+import android.os.Bundle;
+
+import com.android.internal.app.IVoiceInteractorCallback;
+import com.android.internal.app.IVoiceInteractorRequest;
+
+/**
+ * @hide
+ */
+interface IVoiceInteractionSession {
+}
diff --git a/core/java/android/service/voice/IVoiceInteractionSessionService.aidl b/core/java/android/service/voice/IVoiceInteractionSessionService.aidl
new file mode 100644
index 0000000..2519442
--- /dev/null
+++ b/core/java/android/service/voice/IVoiceInteractionSessionService.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.voice;
+
+import android.os.Bundle;
+
+import android.service.voice.IVoiceInteractionSession;
+
+/**
+ * @hide
+ */
+oneway interface IVoiceInteractionSessionService {
+ void newSession(IBinder token, in Bundle args);
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
new file mode 100644
index 0000000..d005890
--- /dev/null
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -0,0 +1,77 @@
+/**
+ * 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.voice;
+
+import android.annotation.SdkConstant;
+import android.app.Instrumentation;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import com.android.internal.app.IVoiceInteractionManagerService;
+
+public class VoiceInteractionService extends Service {
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ * To be supported, the service must also require the
+ * {@link android.Manifest.permission#BIND_VOICE_INTERACTION} permission so
+ * that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.voice.VoiceInteractionService";
+
+ /**
+ * Name under which a VoiceInteractionService component publishes information about itself.
+ * This meta-data should reference an XML resource containing a
+ * <code>&lt;{@link
+ * android.R.styleable#VoiceInteractionService voice-interaction-service}&gt;</code> tag.
+ */
+ public static final String SERVICE_META_DATA = "android.voice_interaction";
+
+ IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() {
+ };
+
+ IVoiceInteractionManagerService mSystemService;
+
+ public void startVoiceActivity(Intent intent, Bundle sessionArgs) {
+ try {
+ mSystemService.startVoiceActivity(intent,
+ intent.resolveType(getContentResolver()),
+ mInterface, sessionArgs);
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mInterface.asBinder();
+ }
+ return null;
+ }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
new file mode 100644
index 0000000..a909ead
--- /dev/null
+++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
@@ -0,0 +1,125 @@
+/*
+ * 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.voice;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.speech.RecognitionService;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/** @hide */
+public class VoiceInteractionServiceInfo {
+ static final String TAG = "VoiceInteractionServiceInfo";
+
+ private String mParseError;
+
+ private ServiceInfo mServiceInfo;
+ private String mSessionService;
+ private String mSettingsActivity;
+
+ public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp)
+ throws PackageManager.NameNotFoundException {
+ this(pm, pm.getServiceInfo(comp, PackageManager.GET_META_DATA));
+ }
+
+ public VoiceInteractionServiceInfo(PackageManager pm, ServiceInfo si) {
+ if (!Manifest.permission.BIND_VOICE_INTERACTION.equals(si.permission)) {
+ mParseError = "Service does not require permission "
+ + Manifest.permission.BIND_VOICE_INTERACTION;
+ return;
+ }
+
+ XmlResourceParser parser = null;
+ try {
+ parser = si.loadXmlMetaData(pm, VoiceInteractionService.SERVICE_META_DATA);
+ if (parser == null) {
+ mParseError = "No " + VoiceInteractionService.SERVICE_META_DATA
+ + " meta-data for " + si.packageName;
+ return;
+ }
+
+ 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 (!"voice-interaction-service".equals(nodeName)) {
+ mParseError = "Meta-data does not start with voice-interaction-service tag";
+ return;
+ }
+
+ TypedArray array = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.VoiceInteractionService);
+ mSessionService = array.getString(
+ com.android.internal.R.styleable.VoiceInteractionService_sessionService);
+ mSettingsActivity = array.getString(
+ com.android.internal.R.styleable.VoiceInteractionService_settingsActivity);
+ array.recycle();
+ if (mSessionService == null) {
+ mParseError = "No sessionService specified";
+ return;
+ }
+ } catch (XmlPullParserException e) {
+ mParseError = "Error parsing voice interation service meta-data: " + e;
+ Log.w(TAG, "error parsing voice interaction service meta-data", e);
+ return;
+ } catch (IOException e) {
+ mParseError = "Error parsing voice interation service meta-data: " + e;
+ Log.w(TAG, "error parsing voice interaction service meta-data", e);
+ return;
+ } catch (PackageManager.NameNotFoundException e) {
+ mParseError = "Error parsing voice interation service meta-data: " + e;
+ Log.w(TAG, "error parsing voice interaction service meta-data", e);
+ return;
+ } finally {
+ if (parser != null) parser.close();
+ }
+ mServiceInfo = si;
+ }
+
+ public String getParseError() {
+ return mParseError;
+ }
+
+ public ServiceInfo getServiceInfo() {
+ return mServiceInfo;
+ }
+
+ public String getSessionService() {
+ return mSessionService;
+ }
+
+ public String getSettingsActivity() {
+ return mSettingsActivity;
+ }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
new file mode 100644
index 0000000..963b6b4
--- /dev/null
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -0,0 +1,195 @@
+/**
+ * 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.voice;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.app.IVoiceInteractorCallback;
+import com.android.internal.app.IVoiceInteractorRequest;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+public abstract class VoiceInteractionSession {
+ static final String TAG = "VoiceInteractionSession";
+ static final boolean DEBUG = true;
+
+ final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() {
+ @Override
+ public IVoiceInteractorRequest startConfirmation(String callingPackage,
+ IVoiceInteractorCallback callback, String prompt, Bundle extras) {
+ Request request = findRequest(callback, true);
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_CONFIRMATION,
+ new Caller(callingPackage, Binder.getCallingUid()), request,
+ prompt, extras));
+ return request.mInterface;
+ }
+
+ @Override
+ public IVoiceInteractorRequest startCommand(String callingPackage,
+ IVoiceInteractorCallback callback, String command, Bundle extras) {
+ Request request = findRequest(callback, true);
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMMAND,
+ new Caller(callingPackage, Binder.getCallingUid()), request,
+ command, extras));
+ return request.mInterface;
+ }
+
+ @Override
+ public boolean[] supportsCommands(String callingPackage, String[] commands) {
+ Message msg = mHandlerCaller.obtainMessageIOO(MSG_SUPPORTS_COMMANDS,
+ 0, new Caller(callingPackage, Binder.getCallingUid()), commands);
+ SomeArgs args = mHandlerCaller.sendMessageAndWait(msg);
+ if (args != null) {
+ boolean[] res = (boolean[])args.arg1;
+ args.recycle();
+ return res;
+ }
+ return new boolean[commands.length];
+ }
+ };
+
+ final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() {
+ };
+
+ public static class Request {
+ final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() {
+ @Override
+ public void cancel() throws RemoteException {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this));
+ }
+ };
+ final IVoiceInteractorCallback mCallback;
+ final HandlerCaller mHandlerCaller;
+ Request(IVoiceInteractorCallback callback, HandlerCaller handlerCaller) {
+ mCallback = callback;
+ mHandlerCaller = handlerCaller;
+ }
+
+ public void sendConfirmResult(boolean confirmed, Bundle result) {
+ try {
+ if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
+ + " confirmed=" + confirmed + " result=" + result);
+ mCallback.deliverConfirmationResult(mInterface, confirmed, result);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void sendCommandResult(boolean complete, Bundle result) {
+ try {
+ if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface
+ + " result=" + result);
+ mCallback.deliverCommandResult(mInterface, complete, result);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void sendCancelResult() {
+ try {
+ if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface);
+ mCallback.deliverCancel(mInterface);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ public static class Caller {
+ final String packageName;
+ final int uid;
+
+ Caller(String _packageName, int _uid) {
+ packageName = _packageName;
+ uid = _uid;
+ }
+ }
+
+ static final int MSG_START_CONFIRMATION = 1;
+ static final int MSG_START_COMMAND = 2;
+ static final int MSG_SUPPORTS_COMMANDS = 3;
+ static final int MSG_CANCEL = 4;
+
+ final Context mContext;
+ final HandlerCaller mHandlerCaller;
+ final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() {
+ @Override
+ public void executeMessage(Message msg) {
+ SomeArgs args = (SomeArgs)msg.obj;
+ switch (msg.what) {
+ case MSG_START_CONFIRMATION:
+ if (DEBUG) Log.d(TAG, "onConfirm: req=" + ((Request) args.arg2).mInterface
+ + " prompt=" + args.arg3 + " extras=" + args.arg4);
+ onConfirm((Caller)args.arg1, (Request)args.arg2, (String)args.arg3,
+ (Bundle)args.arg4);
+ break;
+ case MSG_START_COMMAND:
+ if (DEBUG) Log.d(TAG, "onCommand: req=" + ((Request) args.arg2).mInterface
+ + " command=" + args.arg3 + " extras=" + args.arg4);
+ onCommand((Caller) args.arg1, (Request) args.arg2, (String) args.arg3,
+ (Bundle) args.arg4);
+ break;
+ case MSG_SUPPORTS_COMMANDS:
+ if (DEBUG) Log.d(TAG, "onGetSupportedCommands: cmds=" + args.arg2);
+ args.arg1 = onGetSupportedCommands((Caller) args.arg1, (String[]) args.arg2);
+ break;
+ case MSG_CANCEL:
+ if (DEBUG) Log.d(TAG, "onCancel: req=" + ((Request) args.arg1).mInterface);
+ onCancel((Request)args.arg1);
+ break;
+ }
+ }
+ };
+
+ final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
+
+ public VoiceInteractionSession(Context context) {
+ this(context, new Handler());
+ }
+
+ public VoiceInteractionSession(Context context, Handler handler) {
+ mContext = context;
+ mHandlerCaller = new HandlerCaller(context, handler.getLooper(),
+ mHandlerCallerCallback, true);
+ }
+
+ Request findRequest(IVoiceInteractorCallback callback, boolean newRequest) {
+ synchronized (this) {
+ Request req = mActiveRequests.get(callback.asBinder());
+ if (req != null) {
+ if (newRequest) {
+ throw new IllegalArgumentException("Given request callback " + callback
+ + " is already active");
+ }
+ return req;
+ }
+ req = new Request(callback, mHandlerCaller);
+ mActiveRequests.put(callback.asBinder(), req);
+ return req;
+ }
+ }
+
+ public abstract boolean[] onGetSupportedCommands(Caller caller, String[] commands);
+ public abstract void onConfirm(Caller caller, Request request, String prompt, Bundle extras);
+ public abstract void onCommand(Caller caller, Request request, String command, Bundle extras);
+ public abstract void onCancel(Request request);
+}
diff --git a/core/java/android/service/voice/VoiceInteractionSessionService.java b/core/java/android/service/voice/VoiceInteractionSessionService.java
new file mode 100644
index 0000000..40e5bba
--- /dev/null
+++ b/core/java/android/service/voice/VoiceInteractionSessionService.java
@@ -0,0 +1,82 @@
+/**
+ * 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.voice;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+public abstract class VoiceInteractionSessionService extends Service {
+
+ static final int MSG_NEW_SESSION = 1;
+
+ IVoiceInteractionManagerService mSystemService;
+
+ IVoiceInteractionSessionService mInterface = new IVoiceInteractionSessionService.Stub() {
+ public void newSession(IBinder token, Bundle args) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(MSG_NEW_SESSION,
+ token, args));
+
+ }
+ };
+
+ HandlerCaller mHandlerCaller;
+ final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() {
+ @Override
+ public void executeMessage(Message msg) {
+ SomeArgs args = (SomeArgs)msg.obj;
+ switch (msg.what) {
+ case MSG_NEW_SESSION:
+ doNewSession((IBinder)args.arg1, (Bundle)args.arg2);
+ break;
+ }
+ }
+ };
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+ mHandlerCaller = new HandlerCaller(this, Looper.myLooper(),
+ mHandlerCallerCallback, true);
+ }
+
+ public abstract VoiceInteractionSession onNewSession(Bundle args);
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mInterface.asBinder();
+ }
+
+ void doNewSession(IBinder token, Bundle args) {
+ VoiceInteractionSession session = onNewSession(args);
+ try {
+ mSystemService.deliverNewSession(token, session.mSession, session.mInteractor);
+ } catch (RemoteException e) {
+ }
+ }
+}
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 814326c..0db00f0 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/format/Formatter.java b/core/java/android/text/format/Formatter.java
index 9c98b98..b0cbcd2 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -106,4 +106,69 @@ public final class Formatter {
public static String formatIpAddress(int ipv4Address) {
return NetworkUtils.intToInetAddress(ipv4Address).getHostAddress();
}
+
+ private static final int SECONDS_PER_MINUTE = 60;
+ private static final int SECONDS_PER_HOUR = 60 * 60;
+ private static final int SECONDS_PER_DAY = 24 * 60 * 60;
+
+ /**
+ * Returns elapsed time for the given millis, in the following format:
+ * 1 day 5 hrs; will include at most two units, can go down to seconds precision.
+ * @param context the application context
+ * @param millis the elapsed time in milli seconds
+ * @return the formatted elapsed time
+ * @hide
+ */
+ public static String formatShortElapsedTime(Context context, long millis) {
+ long secondsLong = millis / 1000;
+
+ int days = 0, hours = 0, minutes = 0;
+ if (secondsLong >= SECONDS_PER_DAY) {
+ days = (int)(secondsLong / SECONDS_PER_DAY);
+ secondsLong -= days * SECONDS_PER_DAY;
+ }
+ if (secondsLong >= SECONDS_PER_HOUR) {
+ hours = (int)(secondsLong / SECONDS_PER_HOUR);
+ secondsLong -= hours * SECONDS_PER_HOUR;
+ }
+ if (secondsLong >= SECONDS_PER_MINUTE) {
+ minutes = (int)(secondsLong / SECONDS_PER_MINUTE);
+ secondsLong -= minutes * SECONDS_PER_MINUTE;
+ }
+ int seconds = (int)secondsLong;
+
+ if (days >= 2) {
+ days += (hours+12)/24;
+ return context.getString(com.android.internal.R.string.durationDays, days);
+ } else if (days > 0) {
+ if (hours == 1) {
+ return context.getString(com.android.internal.R.string.durationDayHour, days, hours);
+ }
+ return context.getString(com.android.internal.R.string.durationDayHours, days, hours);
+ } else if (hours >= 2) {
+ hours += (minutes+30)/60;
+ return context.getString(com.android.internal.R.string.durationHours, hours);
+ } else if (hours > 0) {
+ if (minutes == 1) {
+ return context.getString(com.android.internal.R.string.durationHourMinute, hours,
+ minutes);
+ }
+ return context.getString(com.android.internal.R.string.durationHourMinutes, hours,
+ minutes);
+ } else if (minutes >= 2) {
+ minutes += (seconds+30)/60;
+ return context.getString(com.android.internal.R.string.durationMinutes, minutes);
+ } else if (minutes > 0) {
+ if (seconds == 1) {
+ return context.getString(com.android.internal.R.string.durationMinuteSecond, minutes,
+ seconds);
+ }
+ return context.getString(com.android.internal.R.string.durationMinuteSeconds, minutes,
+ seconds);
+ } else if (seconds == 1) {
+ return context.getString(com.android.internal.R.string.durationSecond, seconds);
+ } else {
+ return context.getString(com.android.internal.R.string.durationSeconds, seconds);
+ }
+ }
}
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/ChangeClipBounds.java b/core/java/android/transition/ChangeClipBounds.java
new file mode 100644
index 0000000..a61b29d
--- /dev/null
+++ b/core/java/android/transition/ChangeClipBounds.java
@@ -0,0 +1,96 @@
+/*
+ * 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.ObjectAnimator;
+import android.animation.RectEvaluator;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * ChangeClipBounds captures the {@link android.view.View#getClipBounds()} before and after the
+ * scene change and animates those changes during the transition.
+ */
+public class ChangeClipBounds extends Transition {
+
+ private static final String TAG = "ChangeTransform";
+
+ private static final String PROPNAME_CLIP = "android:clipBounds:clip";
+ private static final String PROPNAME_BOUNDS = "android:clipBounds:bounds";
+
+ private static final String[] sTransitionProperties = {
+ PROPNAME_CLIP,
+ };
+
+ @Override
+ public String[] getTransitionProperties() {
+ return sTransitionProperties;
+ }
+
+ private void captureValues(TransitionValues values) {
+ View view = values.view;
+ if (view.getVisibility() == View.GONE) {
+ return;
+ }
+
+ Rect clip = view.getClipBounds();
+ values.values.put(PROPNAME_CLIP, clip);
+ if (clip == null) {
+ Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight());
+ values.values.put(PROPNAME_BOUNDS, bounds);
+ }
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null
+ || !startValues.values.containsKey(PROPNAME_CLIP)
+ || !endValues.values.containsKey(PROPNAME_CLIP)) {
+ return null;
+ }
+ Rect start = (Rect) startValues.values.get(PROPNAME_CLIP);
+ Rect end = (Rect) endValues.values.get(PROPNAME_CLIP);
+ if (start == null && end == null) {
+ return null; // No animation required since there is no clip.
+ }
+
+ if (start == null) {
+ start = (Rect) startValues.values.get(PROPNAME_BOUNDS);
+ } else if (end == null) {
+ end = (Rect) endValues.values.get(PROPNAME_BOUNDS);
+ }
+ if (start.equals(end)) {
+ return null;
+ }
+
+ endValues.view.setClipBounds(start);
+ RectEvaluator evaluator = new RectEvaluator(new Rect());
+ return ObjectAnimator.ofObject(endValues.view, "clipBounds", evaluator, start, end);
+ }
+}
diff --git a/core/java/android/transition/ChangeTransform.java b/core/java/android/transition/ChangeTransform.java
new file mode 100644
index 0000000..85cb2c7
--- /dev/null
+++ b/core/java/android/transition/ChangeTransform.java
@@ -0,0 +1,163 @@
+/*
+ * 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.FloatArrayEvaluator;
+import android.animation.ObjectAnimator;
+import android.util.FloatProperty;
+import android.util.Property;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * This Transition captures scale and rotation for Views before and after the
+ * scene change and animates those changes during the transition.
+ *
+ * <p>ChangeTransform does not work when the pivot changes between scenes, so either the
+ * pivot must be set to prevent automatic pivot adjustment or the View's size must be unchanged.</p>
+ */
+public class ChangeTransform extends Transition {
+
+ private static final String TAG = "ChangeTransform";
+
+ private static final String PROPNAME_SCALE_X = "android:changeTransform:scaleX";
+ private static final String PROPNAME_SCALE_Y = "android:changeTransform:scaleY";
+ private static final String PROPNAME_ROTATION_X = "android:changeTransform:rotationX";
+ private static final String PROPNAME_ROTATION_Y = "android:changeTransform:rotationY";
+ private static final String PROPNAME_ROTATION_Z = "android:changeTransform:rotationZ";
+ private static final String PROPNAME_PIVOT_X = "android:changeTransform:pivotX";
+ private static final String PROPNAME_PIVOT_Y = "android:changeTransform:pivotY";
+
+ private static final String[] sTransitionProperties = {
+ PROPNAME_SCALE_X,
+ PROPNAME_SCALE_Y,
+ PROPNAME_ROTATION_X,
+ PROPNAME_ROTATION_Y,
+ PROPNAME_ROTATION_Z,
+ };
+
+ private static final FloatProperty<View>[] sChangedProperties = new FloatProperty[] {
+ (FloatProperty) View.SCALE_X,
+ (FloatProperty) View.SCALE_Y,
+ (FloatProperty) View.ROTATION_X,
+ (FloatProperty) View.ROTATION_Y,
+ (FloatProperty) View.ROTATION,
+ };
+
+ private static Property<View, float[]> TRANSFORMS = new Property<View, float[]>(float[].class,
+ "transforms") {
+ @Override
+ public float[] get(View object) {
+ return null;
+ }
+
+ @Override
+ public void set(View view, float[] values) {
+ for (int i = 0; i < values.length; i++) {
+ float value = values[i];
+ if (value != Float.NaN) {
+ sChangedProperties[i].setValue(view, value);
+ }
+ }
+ }
+ };
+
+ @Override
+ public String[] getTransitionProperties() {
+ return sTransitionProperties;
+ }
+
+ private void captureValues(TransitionValues values) {
+ View view = values.view;
+ if (view.getVisibility() == View.GONE) {
+ return;
+ }
+
+ values.values.put(PROPNAME_SCALE_X, view.getScaleX());
+ values.values.put(PROPNAME_SCALE_Y, view.getScaleY());
+ values.values.put(PROPNAME_PIVOT_X, view.getPivotX());
+ values.values.put(PROPNAME_PIVOT_Y, view.getPivotY());
+ values.values.put(PROPNAME_ROTATION_X, view.getRotationX());
+ values.values.put(PROPNAME_ROTATION_Y, view.getRotationY());
+ values.values.put(PROPNAME_ROTATION_Z, view.getRotation());
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null
+ || !startValues.values.containsKey(PROPNAME_SCALE_X)
+ || !endValues.values.containsKey(PROPNAME_SCALE_X)
+ || !isPivotSame(startValues, endValues)
+ || !isChanged(startValues, endValues)) {
+ return null;
+ }
+
+ float[] start = createValues(startValues);
+ float[] end = createValues(endValues);
+ for (int i = 0; i < start.length; i++) {
+ if (start[i] == end[i]) {
+ start[i] = Float.NaN;
+ end[i] = Float.NaN;
+ } else {
+ sChangedProperties[i].setValue(endValues.view, start[i]);
+ }
+ }
+ FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[start.length]);
+ return ObjectAnimator.ofObject(endValues.view, TRANSFORMS, evaluator, start, end);
+ }
+
+ private static float[] createValues(TransitionValues transitionValues) {
+ float[] values = new float[sChangedProperties.length];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = (Float) transitionValues.values.get(sTransitionProperties[i]);
+ }
+ return values;
+ }
+
+ private static boolean isPivotSame(TransitionValues startValues, TransitionValues endValues) {
+ float startPivotX = (Float) startValues.values.get(PROPNAME_PIVOT_X);
+ float startPivotY = (Float) startValues.values.get(PROPNAME_PIVOT_Y);
+ float endPivotX = (Float) endValues.values.get(PROPNAME_PIVOT_X);
+ float endPivotY = (Float) endValues.values.get(PROPNAME_PIVOT_Y);
+
+ // We don't support pivot changes, because they could be automatically set
+ // and we can't end the state in an automatic state.
+ return startPivotX == endPivotX && startPivotY == endPivotY;
+ }
+
+ private static boolean isChanged(TransitionValues startValues, TransitionValues endValues) {
+ for (int i = 0; i < sChangedProperties.length; i++) {
+ Object start = startValues.values.get(sTransitionProperties[i]);
+ Object end = endValues.values.get(sTransitionProperties[i]);
+ if (!start.equals(end)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/core/java/android/transition/CircularPropagation.java b/core/java/android/transition/CircularPropagation.java
new file mode 100644
index 0000000..51beb51
--- /dev/null
+++ b/core/java/android/transition/CircularPropagation.java
@@ -0,0 +1,107 @@
+/*
+ * 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 = 3.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;
+
+ long duration = transition.getDuration();
+ if (duration < 0) {
+ duration = 300;
+ }
+
+ return Math.round(duration * 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..e70dc0c 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,79 @@ 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);
+ 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 boolean mCanceled = false;
+ private float mPausedAlpha = -1;
- @Override
- public void onAnimationResume(Animator animation) {
- if (finalOverlayView != null) {
- finalSceneRoot.getOverlay().add(finalOverlayView);
- }
- }
- };
- return createAnimation(view, startAlpha, endAlpha, endListener);
+ public FadeAnimatorListener(View view) {
+ mView = view;
}
- 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(1);
+ }
+ }
- @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(1);
+ }
- @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..e4c3939
--- /dev/null
+++ b/core/java/android/transition/MoveImage.java
@@ -0,0 +1,338 @@
+/*
+ * 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);
+ final ImageView overlayImage = new ImageView(imageView.getContext());
+ final ViewGroupOverlay overlay = sceneRoot.getOverlay();
+ overlay.add(overlayImage);
+ overlayImage.setLeft(0);
+ overlayImage.setTop(0);
+ overlayImage.setRight(sceneRoot.getWidth());
+ overlayImage.setBottom(sceneRoot.getBottom());
+ overlayImage.setScaleType(ImageView.ScaleType.MATRIX);
+ overlayImage.setImageDrawable(matrixClippedDrawable);
+ matrixClippedDrawable.setMatrix(startMatrix);
+ matrixClippedDrawable.setBounds(startBounds);
+ matrixClippedDrawable.setClipRect(startClip);
+
+ imageView.setVisibility(View.INVISIBLE);
+ 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(overlayImage);
+ }
+
+ @Override
+ public void onAnimationPause(Animator animation) {
+ imageView.setVisibility(View.VISIBLE);
+ overlayImage.setVisibility(View.INVISIBLE);
+ }
+
+ @Override
+ public void onAnimationResume(Animator animation) {
+ imageView.setVisibility(View.INVISIBLE);
+ overlayImage.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ onAnimationEnd(animation);
+ }
+ };
+
+ 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..5d38ac8
--- /dev/null
+++ b/core/java/android/transition/SidePropagation.java
@@ -0,0 +1,169 @@
+/*
+ * 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 = 3.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;
+
+ long duration = transition.getDuration();
+ if (duration < 0) {
+ duration = 300;
+ }
+
+ return Math.round(duration * 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..2549fde 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,17 @@ 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>This TransitionSet contains {@link android.transition.Explode} for visibility,
+ * {@link android.transition.ChangeBounds}, {@link android.transition.ChangeTransform},
+ * and {@link android.transition.ChangeClipBounds} for non-<code>ImageView</code>s and
+ * {@link android.transition.MoveImage} for <code>ImageView</code>s:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/transition/explode_move_together.xml MultipleTransform}
+ *
* <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
@@ -78,7 +88,8 @@ import java.util.List;
* transition uses a fadingMode of {@link Fade#OUT} instead of the default
* out-in behavior. Finally, note the use of the <code>targets</code> sub-tag, which
* takes a set of {@link android.R.styleable#TransitionTarget target} tags, each
- * of which lists a specific <code>targetId</code> which this transition acts upon.
+ * of which lists a specific <code>targetId</code>, <code>targetClass</code>,
+ * <code>excludeId</code>, or <code>excludeClass</code>, which this transition acts upon.
* Use of targets is optional, but can be used to either limit the time spent checking
* attributes on unchanging views, or limiting the types of animations run on specific views.
* In this case, we know that only the <code>grayscaleContainer</code> will be
@@ -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 {
@@ -104,6 +116,7 @@ public abstract class Transition implements Cloneable {
ArrayList<Integer> mTargetIdExcludes = null;
ArrayList<View> mTargetExcludes = null;
ArrayList<Class> mTargetTypeExcludes = null;
+ ArrayList<Class> mTargetTypes = null;
ArrayList<Integer> mTargetIdChildExcludes = null;
ArrayList<View> mTargetChildExcludes = null;
ArrayList<Class> mTargetTypeChildExcludes = null;
@@ -148,6 +161,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 +454,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 +519,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 +534,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);
+ }
+ }
}
/**
@@ -532,19 +570,15 @@ public abstract class Transition implements Cloneable {
}
}
}
- if (mTargetIds.size() == 0 && mTargets.size() == 0) {
+ if (mTargetIds.size() == 0 && mTargets.size() == 0 && mTargetTypes == null) {
return true;
}
- if (mTargetIds.size() > 0) {
- for (int i = 0; i < mTargetIds.size(); ++i) {
- if (mTargetIds.get(i) == targetId) {
- return true;
- }
- }
+ if (mTargetIds.contains((int) targetId) || mTargets.contains(target)) {
+ return true;
}
- if (target != null && mTargets.size() > 0) {
- for (int i = 0; i < mTargets.size(); ++i) {
- if (mTargets.get(i) == target) {
+ if (mTargetTypes != null) {
+ for (int i = 0; i < mTargetTypes.size(); ++i) {
+ if (mTargetTypes.get(i).isInstance(target)) {
return true;
}
}
@@ -563,7 +597,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
*/
@@ -690,6 +724,36 @@ public abstract class Transition implements Cloneable {
}
/**
+ * Adds the Class of a target view that this Transition is interested in
+ * animating. By default, there are no targetTypes, and a Transition will
+ * listen for changes on every view in the hierarchy below the sceneRoot
+ * of the Scene being transitioned into. Setting targetTypes constrains
+ * the Transition to only listen for, and act on, views with these classes.
+ * Views with different classes will be ignored.
+ *
+ * <p>Note that any View that can be cast to targetType will be included, so
+ * if targetType is <code>View.class</code>, all Views will be included.</p>
+ *
+ * @see #addTarget(int)
+ * @see #addTarget(android.view.View)
+ * @see #excludeTarget(Class, boolean)
+ * @see #excludeChildren(Class, boolean)
+ *
+ * @param targetType The type to include when running this transition.
+ * @return The Transition to which the target class was added.
+ * Returning the same object makes it easier to chain calls during
+ * construction, such as
+ * <code>transitionSet.addTransitions(new Fade()).addTarget(ImageView.class);</code>
+ */
+ public Transition addTarget(Class targetType) {
+ if (mTargetTypes == null) {
+ mTargetTypes = new ArrayList<Class>();
+ }
+ mTargetTypes.add(targetType);
+ return this;
+ }
+
+ /**
* Removes the given targetId from the list of ids that this Transition
* is interested in animating.
*
@@ -1008,6 +1072,7 @@ public abstract class Transition implements Cloneable {
} else {
captureEndValues(values);
}
+ capturePropagationValues(values);
if (start) {
mStartValues.viewValues.put(view, values);
if (id >= 0) {
@@ -1033,6 +1098,7 @@ public abstract class Transition implements Cloneable {
} else {
captureEndValues(values);
}
+ capturePropagationValues(values);
if (start) {
mStartValues.viewValues.put(view, values);
} else {
@@ -1109,30 +1175,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 +1263,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 +1294,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 +1402,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 +1535,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 && !transitionValues.values.isEmpty()) {
+ 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 +1636,10 @@ public abstract class Transition implements Cloneable {
mCanRemoveViews = canRemoveViews;
}
+ public boolean canRemoveViews() {
+ return mCanRemoveViews;
+ }
+
@Override
public String toString() {
return toString("");
@@ -1629,16 +1802,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 +1864,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..a5e960a 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -145,7 +145,19 @@ 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 ("changeTransform".equals(name)) {
+ transition = new ChangeTransform();
+ newTransition = true;
+ } else if ("changeClipBounds".equals(name)) {
+ transition = new ChangeClipBounds();
newTransition = true;
} else if ("autoTransition".equals(name)) {
transition = new AutoTransition();
@@ -188,6 +200,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 {
@@ -195,7 +216,6 @@ public class TransitionInflater {
int type;
int depth = parser.getDepth();
- ArrayList<Integer> targetIds = new ArrayList<Integer>();
while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
&& type != XmlPullParser.END_DOCUMENT) {
@@ -210,18 +230,31 @@ public class TransitionInflater {
int id = a.getResourceId(
com.android.internal.R.styleable.TransitionTarget_targetId, -1);
if (id >= 0) {
- targetIds.add(id);
+ transition.addTarget(id);
+ } else if ((id = a.getResourceId(
+ com.android.internal.R.styleable.TransitionTarget_excludeId, -1)) >= 0) {
+ transition.excludeTarget(id, true);
+ } else {
+ String className = a.getString(
+ com.android.internal.R.styleable.TransitionTarget_excludeClass);
+ try {
+ if (className != null) {
+ Class clazz = Class.forName(className);
+ transition.excludeTarget(clazz, true);
+ } else if ((className = a.getString(
+ com.android.internal.R.styleable.TransitionTarget_targetClass))
+ != null) {
+ Class clazz = Class.forName(className);
+ transition.addTarget(clazz);
+ }
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Could not create " + className, e);
+ }
}
} else {
throw new RuntimeException("Unknown scene name: " + parser.getName());
}
}
- int numTargets = targetIds.size();
- if (numTargets > 0) {
- for (int i = 0; i < numTargets; ++i) {
- transition.addTarget(targetIds.get(i));
- }
- }
}
private Transition loadTransition(Transition transition, AttributeSet attrs)
@@ -284,25 +317,23 @@ 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 " +
- "for transition ID " + transitionId);
- } else {
- transitionManager.setTransition(fromScene, toScene, transition);
- }
- } else if (toId >= 0) {
+ if (toScene == null) {
+ throw new RuntimeException("No toScene for transition ID " + transitionId);
+ }
+ if (fromScene == null) {
transitionManager.setTransition(toScene, 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..ce3cc2f 100644
--- a/core/java/android/transition/TransitionManager.java
+++ b/core/java/android/transition/TransitionManager.java
@@ -67,6 +67,8 @@ 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, ArrayMap<Scene, Transition>> mScenePairTransitions =
new ArrayMap<Scene, ArrayMap<Scene, Transition>>();
@@ -253,7 +255,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 +288,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 +304,7 @@ public class TransitionManager {
if (runningTransitions != null && runningTransitions.size() > 0) {
for (Transition runningTransition : runningTransitions) {
- runningTransition.pause();
+ runningTransition.pause(sceneRoot);
}
}
@@ -329,7 +331,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..9081234 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;
@@ -255,11 +256,45 @@ public class TransitionSet extends Transition {
@Override
protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
TransitionValuesMaps endValues) {
+ startValues = removeExcludes(startValues);
+ endValues = removeExcludes(endValues);
for (Transition childTransition : mTransitions) {
childTransition.createAnimators(sceneRoot, startValues, endValues);
}
}
+ private TransitionValuesMaps removeExcludes(TransitionValuesMaps values) {
+ if (mTargetIds.isEmpty() && mTargetIdExcludes == null && mTargetTypeExcludes == null
+ && mTargets.isEmpty()) {
+ return values;
+ }
+ TransitionValuesMaps included = new TransitionValuesMaps();
+ int numValues = values.viewValues.size();
+ for (int i = 0; i < numValues; i++) {
+ View view = values.viewValues.keyAt(i);
+ if (isValidTarget(view, view.getId())) {
+ included.viewValues.put(view, values.viewValues.valueAt(i));
+ }
+ }
+ numValues = values.idValues.size();
+ for (int i = 0; i < numValues; i++) {
+ int id = values.idValues.keyAt(i);
+ TransitionValues transitionValues = values.idValues.valueAt(i);
+ if (isValidTarget(transitionValues.view, id)) {
+ included.idValues.put(id, transitionValues);
+ }
+ }
+ numValues = values.itemIdValues.size();
+ for (int i = 0; i < numValues; i++) {
+ long id = values.itemIdValues.keyAt(i);
+ TransitionValues transitionValues = values.itemIdValues.valueAt(i);
+ if (isValidTarget(transitionValues.view, id)) {
+ included.itemIdValues.put(id, transitionValues);
+ }
+ }
+ return included;
+ }
+
/**
* @hide
*/
@@ -315,23 +350,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 +409,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 +445,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..6e6496c 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
@@ -100,14 +109,14 @@ public abstract class Visibility extends Transition {
final VisibilityInfo visInfo = new VisibilityInfo();
visInfo.visibilityChange = false;
visInfo.fadeIn = false;
- if (startValues != null) {
+ if (startValues != null && startValues.values.containsKey(PROPNAME_VISIBILITY)) {
visInfo.startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
visInfo.startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT);
} else {
visInfo.startVisibility = -1;
visInfo.startParent = null;
}
- if (endValues != null) {
+ if (endValues != null && endValues.values.containsKey(PROPNAME_VISIBILITY)) {
visInfo.endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
visInfo.endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT);
} else {
@@ -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,145 @@ 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) {
+ int originalVisibility = viewToKeep.getVisibility();
+ viewToKeep.setVisibility(View.VISIBLE);
+ Animator animator = onDisappear(sceneRoot, viewToKeep, startValues, endValues);
+ if (animator == null) {
+ viewToKeep.setVisibility(originalVisibility);
+ } 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..538f8a1
--- /dev/null
+++ b/core/java/android/tv/ITvInputClient.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.content.ComponentName;
+import android.tv.ITvInputSession;
+import android.view.InputChannel;
+
+/**
+ * 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, in InputChannel channel, 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..a4c99e4
--- /dev/null
+++ b/core/java/android/tv/ITvInputManager.aidl
@@ -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.tv;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+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);
+
+ void createOverlayView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
+ int userId);
+ void relayoutOverlayView(in IBinder sessionToken, in Rect frame, int userId);
+ void removeOverlayView(in IBinder sessionToken, int userId);
+}
diff --git a/core/java/android/tv/ITvInputService.aidl b/core/java/android/tv/ITvInputService.aidl
new file mode 100644
index 0000000..4f1bc2b
--- /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.ITvInputSessionCallback;
+import android.view.InputChannel;
+
+/**
+ * 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(in InputChannel channel, 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..32fee4b
--- /dev/null
+++ b/core/java/android/tv/ITvInputSession.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.graphics.Rect;
+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);
+
+ void createOverlayView(in IBinder windowToken, in Rect frame);
+ void relayoutOverlayView(in Rect frame);
+ void removeOverlayView();
+}
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..3ccccf3
--- /dev/null
+++ b/core/java/android/tv/ITvInputSessionWrapper.java
@@ -0,0 +1,179 @@
+/*
+ * 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.graphics.Rect;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.tv.TvInputManager.Session;
+import android.tv.TvInputService.TvInputSessionImpl;
+import android.util.Log;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Surface;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+/**
+ * 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 static final int DO_CREATE_OVERLAY_VIEW = 5;
+ private static final int DO_RELAYOUT_OVERLAY_VIEW = 6;
+ private static final int DO_REMOVE_OVERLAY_VIEW = 7;
+
+ private final HandlerCaller mCaller;
+
+ private TvInputSessionImpl mTvInputSessionImpl;
+ private InputChannel mChannel;
+ private TvInputEventReceiver mReceiver;
+
+ public ITvInputSessionWrapper(Context context, TvInputSessionImpl sessionImpl,
+ InputChannel channel) {
+ mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */);
+ mTvInputSessionImpl = sessionImpl;
+ mChannel = channel;
+ if (channel != null) {
+ mReceiver = new TvInputEventReceiver(channel, context.getMainLooper());
+ }
+ }
+
+ @Override
+ public void executeMessage(Message msg) {
+ if (mTvInputSessionImpl == null) {
+ return;
+ }
+
+ switch (msg.what) {
+ case DO_RELEASE: {
+ mTvInputSessionImpl.release();
+ mTvInputSessionImpl = null;
+ if (mReceiver != null) {
+ mReceiver.dispose();
+ mReceiver = null;
+ }
+ if (mChannel != null) {
+ mChannel.dispose();
+ mChannel = null;
+ }
+ return;
+ }
+ case DO_SET_SURFACE: {
+ mTvInputSessionImpl.setSurface((Surface) msg.obj);
+ return;
+ }
+ case DO_SET_VOLUME: {
+ mTvInputSessionImpl.setVolume((Float) msg.obj);
+ return;
+ }
+ case DO_TUNE: {
+ mTvInputSessionImpl.tune((Uri) msg.obj);
+ return;
+ }
+ case DO_CREATE_OVERLAY_VIEW: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mTvInputSessionImpl.createOverlayView((IBinder) args.arg1, (Rect) args.arg2);
+ args.recycle();
+ return;
+ }
+ case DO_RELAYOUT_OVERLAY_VIEW: {
+ mTvInputSessionImpl.relayoutOverlayView((Rect) msg.obj);
+ return;
+ }
+ case DO_REMOVE_OVERLAY_VIEW: {
+ mTvInputSessionImpl.removeOverlayView(true);
+ 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));
+ }
+
+ @Override
+ public void createOverlayView(IBinder windowToken, Rect frame) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_OVERLAY_VIEW, windowToken,
+ frame));
+ }
+
+ @Override
+ public void relayoutOverlayView(Rect frame) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_OVERLAY_VIEW, frame));
+ }
+
+ @Override
+ public void removeOverlayView() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_OVERLAY_VIEW));
+ }
+
+ private final class TvInputEventReceiver extends InputEventReceiver {
+ public TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ if (mTvInputSessionImpl == null) {
+ // The session has been finished.
+ finishInputEvent(event, false);
+ return;
+ }
+
+ int handled = mTvInputSessionImpl.dispatchInputEvent(event, this);
+ if (handled != Session.DISPATCH_IN_PROGRESS) {
+ finishInputEvent(event, handled == Session.DISPATCH_HANDLED);
+ }
+ }
+ }
+}
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..7b9b1fb
--- /dev/null
+++ b/core/java/android/tv/TvInputManager.java
@@ -0,0 +1,742 @@
+/*
+ * 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.graphics.Rect;
+import android.net.Uri;
+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.Pools.Pool;
+import android.util.Pools.SimplePool;
+import android.util.SparseArray;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventSender;
+import android.view.Surface;
+import android.view.View;
+
+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, InputChannel channel,
+ 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(token, channel, 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 {
+ static final int DISPATCH_IN_PROGRESS = -1;
+ static final int DISPATCH_NOT_HANDLED = 0;
+ static final int DISPATCH_HANDLED = 1;
+
+ private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
+
+ private final ITvInputManager mService;
+ private final int mUserId;
+
+ // For scheduling input event handling on the main thread. This also serves as a lock to
+ // protect pending input events and the input channel.
+ private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
+
+ private final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20);
+ private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20);
+
+ private IBinder mToken;
+ private TvInputEventSender mSender;
+ private InputChannel mChannel;
+
+ /** @hide */
+ private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId) {
+ mToken = token;
+ mChannel = channel;
+ mService = service;
+ mUserId = userId;
+ }
+
+ /**
+ * Releases this session.
+ *
+ * @throws IllegalStateException if the session has been already released.
+ */
+ public void release() {
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ try {
+ mService.releaseSession(mToken, mUserId);
+ mToken = null;
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+
+ synchronized (mHandler) {
+ if (mChannel != null) {
+ if (mSender != null) {
+ flushPendingEventsLocked();
+ mSender.dispose();
+ mSender = null;
+ }
+ mChannel.dispose();
+ mChannel = null;
+ }
+ }
+ }
+
+ /**
+ * Sets the {@link android.view.Surface} for this session.
+ *
+ * @param surface A {@link android.view.Surface} used to render video.
+ * @throws IllegalStateException if the session has been already released.
+ */
+ void setSurface(Surface surface) {
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ // 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.
+ * @throws IllegalStateException if the session has been already released.
+ */
+ public void setVolume(float volume) {
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ 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}.
+ * @throws IllegalStateException if the session has been already released.
+ */
+ public void tune(Uri channelUri) {
+ if (channelUri == null) {
+ throw new IllegalArgumentException("channelUri cannot be null");
+ }
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ try {
+ mService.tune(mToken, channelUri, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView}
+ * should be called whenever the layout of its containing view is changed.
+ * {@link #removeOverlayView()} should be called to remove the overlay view.
+ * Since a session can have only one overlay view, this method should be called only once
+ * or it can be called again after calling {@link #removeOverlayView()}.
+ *
+ * @param view A view playing TV.
+ * @param frame A position of the overlay view.
+ * @throws IllegalArgumentException if any of the arguments is {@code null}.
+ * @throws IllegalStateException if {@code view} is not attached to a window or
+ * if the session has been already released.
+ */
+ void createOverlayView(View view, Rect frame) {
+ if (view == null) {
+ throw new IllegalArgumentException("view cannot be null");
+ }
+ if (frame == null) {
+ throw new IllegalArgumentException("frame cannot be null");
+ }
+ if (view.getWindowToken() == null) {
+ throw new IllegalStateException("view must be attached to a window");
+ }
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ try {
+ mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Relayouts the current overlay view.
+ *
+ * @param frame A new position of the overlay view.
+ * @throws IllegalArgumentException if the arguments is {@code null}.
+ * @throws IllegalStateException if the session has been already released.
+ */
+ void relayoutOverlayView(Rect frame) {
+ if (frame == null) {
+ throw new IllegalArgumentException("frame cannot be null");
+ }
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ try {
+ mService.relayoutOverlayView(mToken, frame, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Removes the current overlay view.
+ *
+ * @throws IllegalStateException if the session has been already released.
+ */
+ void removeOverlayView() {
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ try {
+ mService.removeOverlayView(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Dispatches an input event to this session.
+ *
+ * @param event {@link InputEvent} to dispatch.
+ * @param token A token used to identify the input event later in the callback.
+ * @param callback A callback used to receive the dispatch result.
+ * @param handler {@link Handler} that the dispatch result will be delivered to.
+ * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
+ * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
+ * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
+ * be invoked later.
+ * @throws IllegalArgumentException if any of the necessary arguments is {@code null}.
+ * @hide
+ */
+ public int dispatchInputEvent(InputEvent event, Object token,
+ FinishedInputEventCallback callback, Handler handler) {
+ if (event == null) {
+ throw new IllegalArgumentException("event cannot be null");
+ }
+ if (callback != null && handler == null) {
+ throw new IllegalArgumentException("handler cannot be null");
+ }
+ synchronized (mHandler) {
+ if (mChannel == null) {
+ return DISPATCH_NOT_HANDLED;
+ }
+ PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ // Already running on the main thread so we can send the event immediately.
+ return sendInputEventOnMainLooperLocked(p);
+ }
+
+ // Post the event to the main thread.
+ Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ return DISPATCH_IN_PROGRESS;
+ }
+ }
+
+ /**
+ * Callback that is invoked when an input event that was dispatched to this session has been
+ * finished.
+ *
+ * @hide
+ */
+ public interface FinishedInputEventCallback {
+ /**
+ * Called when the dispatched input event is finished.
+ *
+ * @param token a token passed to {@link #dispatchInputEvent}.
+ * @param handled {@code true} if the dispatched input event was handled properly.
+ * {@code false} otherwise.
+ */
+ public void onFinishedInputEvent(Object token, boolean handled);
+ }
+
+ // Must be called on the main looper
+ private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
+ synchronized (mHandler) {
+ int result = sendInputEventOnMainLooperLocked(p);
+ if (result == DISPATCH_IN_PROGRESS) {
+ return;
+ }
+ }
+
+ invokeFinishedInputEventCallback(p, false);
+ }
+
+ private int sendInputEventOnMainLooperLocked(PendingEvent p) {
+ if (mChannel != null) {
+ if (mSender == null) {
+ mSender = new TvInputEventSender(mChannel, mHandler.getLooper());
+ }
+
+ final InputEvent event = p.mEvent;
+ final int seq = event.getSequenceNumber();
+ if (mSender.sendInputEvent(seq, event)) {
+ mPendingEvents.put(seq, p);
+ Message msg = mHandler.obtainMessage(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
+ return DISPATCH_IN_PROGRESS;
+ }
+
+ Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
+ + event);
+ }
+ return DISPATCH_NOT_HANDLED;
+ }
+
+ void finishedInputEvent(int seq, boolean handled, boolean timeout) {
+ final PendingEvent p;
+ synchronized (mHandler) {
+ int index = mPendingEvents.indexOfKey(seq);
+ if (index < 0) {
+ return; // spurious, event already finished or timed out
+ }
+
+ p = mPendingEvents.valueAt(index);
+ mPendingEvents.removeAt(index);
+
+ if (timeout) {
+ Log.w(TAG, "Timeout waiting for seesion to handle input event after "
+ + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
+ } else {
+ mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+ }
+ }
+
+ invokeFinishedInputEventCallback(p, handled);
+ }
+
+ // Assumes the event has already been removed from the queue.
+ void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
+ p.mHandled = handled;
+ if (p.mHandler.getLooper().isCurrentThread()) {
+ // Already running on the callback handler thread so we can send the callback
+ // immediately.
+ p.run();
+ } else {
+ // Post the event to the callback handler thread.
+ // In this case, the callback will be responsible for recycling the event.
+ Message msg = Message.obtain(p.mHandler, p);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ }
+
+ private void flushPendingEventsLocked() {
+ mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
+
+ final int count = mPendingEvents.size();
+ for (int i = 0; i < count; i++) {
+ int seq = mPendingEvents.keyAt(i);
+ Message msg = mHandler.obtainMessage(InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ }
+
+ private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
+ FinishedInputEventCallback callback, Handler handler) {
+ PendingEvent p = mPendingEventPool.acquire();
+ if (p == null) {
+ p = new PendingEvent();
+ }
+ p.mEvent = event;
+ p.mToken = token;
+ p.mCallback = callback;
+ p.mHandler = handler;
+ return p;
+ }
+
+ private void recyclePendingEventLocked(PendingEvent p) {
+ p.recycle();
+ mPendingEventPool.release(p);
+ }
+
+ private final class InputEventHandler extends Handler {
+ public static final int MSG_SEND_INPUT_EVENT = 1;
+ public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
+ public static final int MSG_FLUSH_INPUT_EVENT = 3;
+
+ InputEventHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SEND_INPUT_EVENT: {
+ sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
+ return;
+ }
+ case MSG_TIMEOUT_INPUT_EVENT: {
+ finishedInputEvent(msg.arg1, false, true);
+ return;
+ }
+ case MSG_FLUSH_INPUT_EVENT: {
+ finishedInputEvent(msg.arg1, false, false);
+ return;
+ }
+ }
+ }
+ }
+
+ private final class TvInputEventSender extends InputEventSender {
+ public TvInputEventSender(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEventFinished(int seq, boolean handled) {
+ finishedInputEvent(seq, handled, false);
+ }
+ }
+
+ private final class PendingEvent implements Runnable {
+ public InputEvent mEvent;
+ public Object mToken;
+ public FinishedInputEventCallback mCallback;
+ public Handler mHandler;
+ public boolean mHandled;
+
+ public void recycle() {
+ mEvent = null;
+ mToken = null;
+ mCallback = null;
+ mHandler = null;
+ mHandled = false;
+ }
+
+ @Override
+ public void run() {
+ mCallback.onFinishedInputEvent(mToken, mHandled);
+
+ synchronized (mHandler) {
+ recyclePendingEventLocked(this);
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java
new file mode 100644
index 0000000..70e7f95
--- /dev/null
+++ b/core/java/android/tv/TvInputService.java
@@ -0,0 +1,551 @@
+/*
+ * 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.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+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.tv.TvInputManager.Session;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
+
+/**
+ * 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(InputChannel channel, ITvInputSessionCallback cb) {
+ if (channel == null) {
+ Log.w(TAG, "Creating session without input channel");
+ }
+ if (cb == null) {
+ return;
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = channel;
+ args.arg2 = cb;
+ mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).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 class TvInputSessionImpl implements KeyEvent.Callback {
+ private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
+ private final WindowManager mWindowManager;
+ private WindowManager.LayoutParams mWindowParams;
+ private Surface mSurface;
+ private View mOverlayView;
+ private boolean mOverlayViewEnabled;
+ private IBinder mWindowToken;
+ private Rect mOverlayFrame;
+
+ public TvInputSessionImpl() {
+ mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ /**
+ * Enables or disables the overlay view. By default, the overlay view is disabled. Must be
+ * called explicitly after the session is created to enable the overlay view.
+ *
+ * @param enable {@code true} if you want to enable the overlay view. {@code false}
+ * otherwise.
+ */
+ public void setOverlayViewEnabled(final boolean enable) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (enable == mOverlayViewEnabled) {
+ return;
+ }
+ mOverlayViewEnabled = enable;
+ if (enable) {
+ if (mWindowToken != null) {
+ createOverlayView(mWindowToken, mOverlayFrame);
+ }
+ } else {
+ removeOverlayView(false);
+ }
+ }
+ });
+ }
+
+ /**
+ * 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);
+
+ /**
+ * Called when an application requests to create an overlay view. Each session
+ * implementation can override this method and return its own view.
+ *
+ * @return a view attached to the overlay window
+ */
+ public View onCreateOverlayView() {
+ return null;
+ }
+
+ /**
+ * Default implementation of {@link android.view.KeyEvent.Callback#onKeyDown(int, KeyEvent)
+ * KeyEvent.Callback.onKeyDown()}: always returns false (doesn't handle the event).
+ * <p>
+ * Override this to intercept key down events before they are processed by the application.
+ * If you return true, the application will not process the event itself. If you return
+ * false, the normal application processing will occur as if the TV input had not seen the
+ * event at all.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Default implementation of
+ * {@link android.view.KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
+ * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle the event).
+ * <p>
+ * Override this to intercept key long press events before they are processed by the
+ * application. If you return true, the application will not process the event itself. If
+ * you return false, the normal application processing will occur as if the TV input had not
+ * seen the event at all.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ @Override
+ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Default implementation of
+ * {@link android.view.KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
+ * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle the event).
+ * <p>
+ * Override this to intercept special key multiple events before they are processed by the
+ * application. If you return true, the application will not itself process the event. If
+ * you return false, the normal application processing will occur as if the TV input had not
+ * seen the event at all.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param count The number of times the action was made.
+ * @param event Description of the key event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ @Override
+ public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Default implementation of {@link android.view.KeyEvent.Callback#onKeyUp(int, KeyEvent)
+ * KeyEvent.Callback.onKeyUp()}: always returns false (doesn't handle the event).
+ * <p>
+ * Override this to intercept key up events before they are processed by the application. If
+ * you return true, the application will not itself process the event. If you return false,
+ * the normal application processing will occur as if the TV input had not seen the event at
+ * all.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle touch screen motion events on the current input session.
+ *
+ * @param event The motion event being received.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ * @see View#onTouchEvent
+ */
+ public boolean onTouchEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle trackball events on the current input session.
+ *
+ * @param event The motion event being received.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ * @see View#onTrackballEvent
+ */
+ public boolean onTrackballEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle generic motion events on the current input session.
+ *
+ * @param event The motion event being received.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ * @see View#onGenericMotionEvent
+ */
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * This method is called when the application would like to stop using the current input
+ * session.
+ */
+ void release() {
+ onRelease();
+ if (mSurface != null) {
+ mSurface.release();
+ mSurface = null;
+ }
+ removeOverlayView(true);
+ }
+
+ /**
+ * Calls {@link #onSetSurface}.
+ */
+ void setSurface(Surface surface) {
+ onSetSurface(surface);
+ if (mSurface != null) {
+ mSurface.release();
+ }
+ mSurface = 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.
+ }
+
+ /**
+ * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach
+ * to the overlay window.
+ *
+ * @param windowToken A window token of an application.
+ * @param frame A position of the overlay view.
+ */
+ void createOverlayView(IBinder windowToken, Rect frame) {
+ if (mOverlayView != null) {
+ mWindowManager.removeView(mOverlayView);
+ mOverlayView = null;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "create overlay view(" + frame + ")");
+ }
+ mWindowToken = windowToken;
+ mOverlayFrame = frame;
+ if (!mOverlayViewEnabled) {
+ return;
+ }
+ mOverlayView = onCreateOverlayView();
+ if (mOverlayView == null) {
+ return;
+ }
+ // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create
+ // an overlay window above the media window but below the application window.
+ int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+ // We make the overlay view non-focusable and non-touchable so that
+ // the application that owns the window token can decide whether to consume or
+ // dispatch the input events.
+ int flag = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ mWindowParams = new WindowManager.LayoutParams(
+ frame.right - frame.left, frame.bottom - frame.top,
+ frame.left, frame.top, type, flag, PixelFormat.TRANSPARENT);
+ mWindowParams.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ mWindowParams.gravity = Gravity.START | Gravity.TOP;
+ mWindowParams.token = windowToken;
+ mWindowManager.addView(mOverlayView, mWindowParams);
+ }
+
+ /**
+ * Relayouts the current overlay view.
+ *
+ * @param frame A new position of the overlay view.
+ */
+ void relayoutOverlayView(Rect frame) {
+ if (DEBUG) {
+ Log.d(TAG, "relayout overlay view(" + frame + ")");
+ }
+ mOverlayFrame = frame;
+ if (!mOverlayViewEnabled || mOverlayView == null) {
+ return;
+ }
+ mWindowParams.x = frame.left;
+ mWindowParams.y = frame.top;
+ mWindowParams.width = frame.right - frame.left;
+ mWindowParams.height = frame.bottom - frame.top;
+ mWindowManager.updateViewLayout(mOverlayView, mWindowParams);
+ }
+
+ /**
+ * Removes the current overlay view.
+ */
+ void removeOverlayView(boolean clearWindowToken) {
+ if (DEBUG) {
+ Log.d(TAG, "remove overlay view(" + mOverlayView + ")");
+ }
+ if (clearWindowToken) {
+ mWindowToken = null;
+ mOverlayFrame = null;
+ }
+ if (mOverlayView != null) {
+ mWindowManager.removeView(mOverlayView);
+ mOverlayView = null;
+ mWindowParams = null;
+ }
+ }
+
+ /**
+ * Takes care of dispatching incoming input events and tells whether the event was handled.
+ */
+ int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
+ if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
+ if (event instanceof KeyEvent) {
+ if (((KeyEvent) event).dispatch(this, mDispatcherState, this)) {
+ return Session.DISPATCH_HANDLED;
+ }
+ } else if (event instanceof MotionEvent) {
+ MotionEvent motionEvent = (MotionEvent) event;
+ final int source = motionEvent.getSource();
+ if (motionEvent.isTouchEvent()) {
+ if (onTouchEvent(motionEvent)) {
+ return Session.DISPATCH_HANDLED;
+ }
+ } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ if (onTrackballEvent(motionEvent)) {
+ return Session.DISPATCH_HANDLED;
+ }
+ } else {
+ if (onGenericMotionEvent(motionEvent)) {
+ return Session.DISPATCH_HANDLED;
+ }
+ }
+ }
+ if (mOverlayView == null) {
+ return Session.DISPATCH_NOT_HANDLED;
+ }
+ if (!mOverlayView.hasWindowFocus()) {
+ mOverlayView.getViewRootImpl().windowFocusChanged(true, true);
+ }
+ mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
+ return Session.DISPATCH_IN_PROGRESS;
+ }
+ }
+
+ 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: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ InputChannel channel = (InputChannel) args.arg1;
+ ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
+ try {
+ TvInputSessionImpl sessionImpl = onCreateSession();
+ if (sessionImpl == null) {
+ // Failed to create a session.
+ cb.onSessionCreated(null);
+ } else {
+ ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
+ sessionImpl, channel);
+ cb.onSessionCreated(stub);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in onSessionCreated");
+ }
+ args.recycle();
+ 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/tv/TvView.java b/core/java/android/tv/TvView.java
new file mode 100644
index 0000000..289823b
--- /dev/null
+++ b/core/java/android/tv/TvView.java
@@ -0,0 +1,360 @@
+/*
+ * 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.Context;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.tv.TvInputManager.Session;
+import android.tv.TvInputManager.Session.FinishedInputEventCallback;
+import android.tv.TvInputManager.SessionCreateCallback;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+/**
+ * View playing TV
+ */
+public class TvView extends SurfaceView {
+ // STOPSHIP: Turn debugging off.
+ private static final boolean DEBUG = true;
+ private static final String TAG = "TvView";
+
+ private final Handler mHandler = new Handler();
+ private TvInputManager.Session mSession;
+ private Surface mSurface;
+ private boolean mOverlayViewCreated;
+ private Rect mOverlayViewFrame;
+ private final TvInputManager mTvInputManager;
+ private SessionCreateCallback mSessionCreateCallback;
+ private OnUnhandledInputEventListener mOnUnhandledInputEventListener;
+
+ private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width=" + width
+ + ", height=" + height + ")");
+ if (holder.getSurface() == mSurface) {
+ return;
+ }
+ mSurface = holder.getSurface();
+ setSessionSurface(mSurface);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ mSurface = holder.getSurface();
+ setSessionSurface(mSurface);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ mSurface = null;
+ setSessionSurface(null);
+ }
+ };
+
+ private final FinishedInputEventCallback mFinishedInputEventCallback =
+ new FinishedInputEventCallback() {
+ @Override
+ public void onFinishedInputEvent(Object token, boolean handled) {
+ if (DEBUG) {
+ Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled=" + handled + ")");
+ }
+ if (handled) {
+ return;
+ }
+ // TODO: Re-order unhandled events.
+ InputEvent event = (InputEvent) token;
+ if (dispatchUnhandledInputEvent(event)) {
+ return;
+ }
+ getViewRootImpl().dispatchUnhandledInputEvent(event);
+ }
+ };
+
+ public TvView(Context context) {
+ this(context, null, 0);
+ }
+
+ public TvView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TvView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ getHolder().addCallback(mSurfaceHolderCallback);
+ mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE);
+ }
+
+ /**
+ * Binds a TV input to this view. {@link SessionCreateCallback#onSessionCreated} will be
+ * called to send the result of this binding with {@link TvInputManager.Session}.
+ * If a TV input is already bound, the input will be unbound from this view and its session
+ * will be released.
+ *
+ * @param name TV input name will be bound to this view.
+ * @param callback called when TV input is bound. The callback sends
+ * {@link TvInputManager.Session}
+ * @throws IllegalArgumentException if any of the arguments is {@code null}.
+ */
+ public void bindTvInput(ComponentName name, SessionCreateCallback callback) {
+ if (name == null) {
+ throw new IllegalArgumentException("name cannot be null");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+ if (mSession != null) {
+ release();
+ }
+ // When bindTvInput is called multiple times before the callback is called,
+ // only the callback of the last bindTvInput call will be actually called back.
+ // The previous callbacks will be ignored. For the logic, mSessionCreateCallback
+ // is newly assigned for every bindTvInput call and compared with
+ // MySessionCreateCallback.this.
+ mSessionCreateCallback = new MySessionCreateCallback(callback);
+ mTvInputManager.createSession(name, mSessionCreateCallback, mHandler);
+ }
+
+ /**
+ * Unbinds a TV input currently bound. Its corresponding {@link TvInputManager.Session}
+ * is released.
+ */
+ public void unbindTvInput() {
+ if (mSession != null) {
+ release();
+ }
+ }
+
+ /**
+ * Dispatches an unhandled input event to the next receiver.
+ * <p>
+ * Except system keys, TvView always consumes input events in the normal flow. This is called
+ * asynchronously from where the event is dispatched. It gives the host application a chance to
+ * dispatch the unhandled input events.
+ *
+ * @param event The input event.
+ * @return {@code true} if the event was handled by the view, {@code false} otherwise.
+ */
+ public boolean dispatchUnhandledInputEvent(InputEvent event) {
+ if (mOnUnhandledInputEventListener != null) {
+ if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) {
+ return true;
+ }
+ }
+ return onUnhandledInputEvent(event);
+ }
+
+ /**
+ * Called when an unhandled input event was also not handled by the user provided callback. This
+ * is the last chance to handle the unhandled input event in the TvView.
+ *
+ * @param event The input event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to be
+ * handled by the next receiver, return {@code false}.
+ */
+ public boolean onUnhandledInputEvent(InputEvent event) {
+ return false;
+ }
+
+ /**
+ * Registers a callback to be invoked when an input event was not handled by the bound TV input.
+ *
+ * @param listener The callback to invoke when the unhandled input event was received.
+ */
+ public void setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener) {
+ mOnUnhandledInputEventListener = listener;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (super.dispatchKeyEvent(event)) {
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "dispatchKeyEvent(" + event + ")");
+ if (mSession == null) {
+ return false;
+ }
+ int ret = mSession.dispatchInputEvent(event, event, mFinishedInputEventCallback, mHandler);
+ return ret != Session.DISPATCH_NOT_HANDLED;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (super.dispatchTouchEvent(event)) {
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "dispatchTouchEvent(" + event + ")");
+ if (mSession == null) {
+ return false;
+ }
+ int ret = mSession.dispatchInputEvent(event, event, mFinishedInputEventCallback, mHandler);
+ return ret != Session.DISPATCH_NOT_HANDLED;
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent event) {
+ if (super.dispatchTrackballEvent(event)) {
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "dispatchTrackballEvent(" + event + ")");
+ if (mSession == null) {
+ return false;
+ }
+ int ret = mSession.dispatchInputEvent(event, event, mFinishedInputEventCallback, mHandler);
+ return ret != Session.DISPATCH_NOT_HANDLED;
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent event) {
+ if (super.dispatchGenericMotionEvent(event)) {
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent(" + event + ")");
+ if (mSession == null) {
+ return false;
+ }
+ int ret = mSession.dispatchInputEvent(event, event, mFinishedInputEventCallback, mHandler);
+ return ret != Session.DISPATCH_NOT_HANDLED;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ createSessionOverlayView();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ removeSessionOverlayView();
+ super.onDetachedFromWindow();
+ }
+
+ /** @hide */
+ @Override
+ protected void updateWindow(boolean force, boolean redrawNeeded) {
+ super.updateWindow(force, redrawNeeded);
+ relayoutSessionOverlayView();
+ }
+
+ private void release() {
+ setSessionSurface(null);
+ removeSessionOverlayView();
+ mSession.release();
+ mSession = null;
+ }
+
+ private void setSessionSurface(Surface surface) {
+ if (mSession == null) {
+ return;
+ }
+ mSession.setSurface(surface);
+ }
+
+ private void createSessionOverlayView() {
+ if (mSession == null || !isAttachedToWindow()
+ || mOverlayViewCreated) {
+ return;
+ }
+ mOverlayViewFrame = getViewFrameOnScreen();
+ mSession.createOverlayView(this, mOverlayViewFrame);
+ mOverlayViewCreated = true;
+ }
+
+ private void removeSessionOverlayView() {
+ if (mSession == null || !mOverlayViewCreated) {
+ return;
+ }
+ mSession.removeOverlayView();
+ mOverlayViewCreated = false;
+ mOverlayViewFrame = null;
+ }
+
+ private void relayoutSessionOverlayView() {
+ if (mSession == null || !isAttachedToWindow()
+ || !mOverlayViewCreated) {
+ return;
+ }
+ Rect viewFrame = getViewFrameOnScreen();
+ if (viewFrame.equals(mOverlayViewFrame)) {
+ return;
+ }
+ mSession.relayoutOverlayView(viewFrame);
+ mOverlayViewFrame = viewFrame;
+ }
+
+ private Rect getViewFrameOnScreen() {
+ int[] location = new int[2];
+ getLocationOnScreen(location);
+ return new Rect(location[0], location[1],
+ location[0] + getWidth(), location[1] + getHeight());
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the unhandled input event is received.
+ */
+ public interface OnUnhandledInputEventListener {
+ /**
+ * Called when an input event was not handled by the bound TV input.
+ * <p>
+ * This is called asynchronously from where the event is dispatched. It gives the host
+ * application a chance to handle the unhandled input events.
+ *
+ * @param event The input event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ boolean onUnhandledInputEvent(InputEvent event);
+ }
+
+ private class MySessionCreateCallback implements SessionCreateCallback {
+ final SessionCreateCallback mExternalCallback;
+
+ MySessionCreateCallback(SessionCreateCallback externalCallback) {
+ mExternalCallback = externalCallback;
+ }
+
+ @Override
+ public void onSessionCreated(Session session) {
+ if (this != mSessionCreateCallback) {
+ // This callback is obsolete.
+ session.release();
+ return;
+ }
+ mSession = session;
+ if (session != null) {
+ // mSurface may not be ready yet as soon as starting an application.
+ // In the case, we don't send Session.setSurface(null) unnecessarily.
+ // setSessionSurface will be called in surfaceCreated.
+ if (mSurface != null) {
+ setSessionSurface(mSurface);
+ }
+ createSessionOverlayView();
+ }
+ if (mExternalCallback != null) {
+ mExternalCallback.onSessionCreated(session);
+ }
+ }
+ }
+}
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..2cc91b9 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,27 @@ 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 GOOD_GTLD_CHAR =
+ "a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
+ private static final String GTLD = "[" + GOOD_GTLD_CHAR + "]{2,63}";
+ private static final String HOST_NAME = "(" + IRI + "\\.)+" + GTLD;
+
+ 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 +147,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 +155,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 +171,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..ba1c4b6 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;
}
/**
@@ -100,7 +96,7 @@ public class ContextThemeWrapper extends ContextWrapper {
return mTheme;
}
- mThemeResource = Resources.selectDefaultTheme(mThemeResource,
+ mThemeResource = getResources().selectDefaultTheme(mThemeResource,
getApplicationInfo().targetSdkVersion);
initializeTheme();
@@ -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..34b85d9 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -18,7 +18,6 @@ package android.view;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.ColorFilter;
import android.graphics.DrawFilter;
import android.graphics.Matrix;
import android.graphics.NinePatch;
@@ -31,7 +30,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Shader;
-import android.graphics.SurfaceTexture;
import android.graphics.TemporaryBuffer;
import android.text.GraphicsOperations;
import android.text.SpannableString;
@@ -46,10 +44,9 @@ class GLES20Canvas extends HardwareCanvas {
private static final int MODIFIER_NONE = 0;
private static final int MODIFIER_SHADOW = 1;
private static final int MODIFIER_SHADER = 2;
- private static final int MODIFIER_COLOR_FILTER = 4;
private final boolean mOpaque;
- private long mRenderer;
+ protected long mRenderer;
// The native renderer will be destroyed when this object dies.
// DO NOT overwrite this reference once it is set.
@@ -88,15 +85,6 @@ class GLES20Canvas extends HardwareCanvas {
GLES20Canvas(boolean translucent) {
this(false, translucent);
}
-
- /**
- * Creates a canvas to render into an FBO.
- */
- GLES20Canvas(long layer, boolean translucent) {
- mOpaque = !translucent;
- mRenderer = nCreateLayerRenderer(layer);
- setupFinalizer();
- }
protected GLES20Canvas(boolean record, boolean translucent) {
mOpaque = !translucent;
@@ -118,12 +106,7 @@ class GLES20Canvas extends HardwareCanvas {
}
}
- protected void resetDisplayListRenderer() {
- nResetDisplayListRenderer(mRenderer);
- }
-
private static native long nCreateRenderer();
- private static native long nCreateLayerRenderer(long layer);
private static native long nCreateDisplayListRenderer();
private static native void nResetDisplayListRenderer(long renderer);
private static native void nDestroyRenderer(long renderer);
@@ -145,13 +128,11 @@ class GLES20Canvas extends HardwareCanvas {
}
}
- @Override
- public void setName(String name) {
- super.setName(name);
- nSetName(mRenderer, name);
+ public static void setProperty(String name, String value) {
+ nSetProperty(name, value);
}
- private static native void nSetName(long renderer, String name);
+ private static native void nSetProperty(String name, String value);
///////////////////////////////////////////////////////////////////////////
// Hardware layers
@@ -159,12 +140,12 @@ class GLES20Canvas extends HardwareCanvas {
@Override
void pushLayerUpdate(HardwareLayer layer) {
- nPushLayerUpdate(mRenderer, ((GLES20RenderLayer) layer).mLayer);
+ nPushLayerUpdate(mRenderer, layer.getLayer());
}
@Override
void cancelLayerUpdate(HardwareLayer layer) {
- nCancelLayerUpdate(mRenderer, ((GLES20RenderLayer) layer).mLayer);
+ nCancelLayerUpdate(mRenderer, layer.getLayer());
}
@Override
@@ -177,22 +158,7 @@ class GLES20Canvas extends HardwareCanvas {
nClearLayerUpdates(mRenderer);
}
- static native long nCreateTextureLayer(boolean opaque, int[] layerInfo);
- static native long nCreateLayer(int width, int height, boolean isOpaque, int[] layerInfo);
- static native boolean nResizeLayer(long layerId, int width, int height, int[] layerInfo);
- static native void nSetOpaqueLayer(long layerId, boolean isOpaque);
- static native void nSetLayerPaint(long layerId, long nativePaint);
- static native void nSetLayerColorFilter(long layerId, long nativeColorFilter);
- static native void nUpdateTextureLayer(long layerId, int width, int height, boolean opaque,
- SurfaceTexture surface);
- static native void nClearLayerTexture(long layerId);
- static native void nSetTextureLayerTransform(long layerId, long matrix);
- static native void nDestroyLayer(long layerId);
- static native void nDestroyLayerDeferred(long layerId);
- static native void nUpdateRenderLayer(long layerId, long renderer, long displayList,
- int left, int top, int right, int bottom);
static native boolean nCopyLayer(long layerId, long bitmap);
-
private static native void nClearLayerUpdates(long renderer);
private static native void nFlushLayerUpdates(long renderer);
private static native void nPushLayerUpdate(long renderer, long layer);
@@ -286,18 +252,6 @@ class GLES20Canvas extends HardwareCanvas {
private static native int nGetStencilSize();
- void setCountOverdrawEnabled(boolean enabled) {
- nSetCountOverdrawEnabled(mRenderer, enabled);
- }
-
- static native void nSetCountOverdrawEnabled(long renderer, boolean enabled);
-
- float getOverdraw() {
- return nGetOverdraw(mRenderer);
- }
-
- static native float nGetOverdraw(long renderer);
-
///////////////////////////////////////////////////////////////////////////
// Functor
///////////////////////////////////////////////////////////////////////////
@@ -402,22 +356,11 @@ class GLES20Canvas extends HardwareCanvas {
// Display list
///////////////////////////////////////////////////////////////////////////
- long getDisplayList(long displayList) {
- return nGetDisplayList(mRenderer, displayList);
- }
-
- private static native long nGetDisplayList(long renderer, long displayList);
-
- @Override
- void outputDisplayList(DisplayList displayList) {
- nOutputDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList());
- }
-
- private static native void nOutputDisplayList(long renderer, long displayList);
+ protected static native long nFinishRecording(long renderer);
@Override
- public int drawDisplayList(DisplayList displayList, Rect dirty, int flags) {
- return nDrawDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList(),
+ public int drawDisplayList(RenderNode displayList, Rect dirty, int flags) {
+ return nDrawDisplayList(mRenderer, displayList.getNativeDisplayList(),
dirty, flags);
}
@@ -430,24 +373,11 @@ 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);
- void interrupt() {
- nInterrupt(mRenderer);
- }
-
- void resume() {
- nResume(mRenderer);
- }
-
- private static native void nInterrupt(long renderer);
- private static native void nResume(long renderer);
-
///////////////////////////////////////////////////////////////////////////
// Support
///////////////////////////////////////////////////////////////////////////
@@ -648,15 +578,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 +588,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 +671,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 +694,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 +704,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 +827,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 +877,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 +917,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 +935,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 +955,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 +973,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 +1040,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 +1090,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 {
@@ -1216,11 +1117,11 @@ 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);
+ public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
+ Paint paint) {
+ int modifiers = setupModifiers(paint, MODIFIER_SHADER);
try {
- nDrawRoundRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
- rx, ry, paint.mNativePaint);
+ nDrawRoundRect(mRenderer, left, top, right, bottom, rx, ry, paint.mNativePaint);
} finally {
if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
@@ -1397,12 +1298,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 +1319,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 +1337,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..97339cc
--- /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
+ 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(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(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..d31c79d 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);
+ 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..05e202b 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;
@@ -646,12 +645,11 @@ public class KeyEvent extends InputEvent implements Parcelable {
// NOTE: If you add a new keycode here you must also add it to:
// isSystem()
// frameworks/native/include/android/keycodes.h
- // frameworks/base/include/androidfw/KeycodeLabels.h
+ // frameworks/base/include/androidfw/InputEventAttributes.h
// external/webkit/WebKit/android/plugins/ANPKeyCodes.h
// frameworks/base/core/res/res/values/attrs.xml
// emulator?
// LAST_KEYCODE
- // KEYCODE_SYMBOLIC_NAMES
//
// Also Android currently does not reserve code ranges for vendor-
// specific key codes. If you have new key codes to have, you
@@ -659,237 +657,6 @@ public class KeyEvent extends InputEvent implements Parcelable {
// those new codes. This is intended to maintain a consistent
// set of key code definitions across all Android devices.
- // Symbolic names of all key codes.
- private static final SparseArray<String> KEYCODE_SYMBOLIC_NAMES = new SparseArray<String>();
- private static void populateKeycodeSymbolicNames() {
- SparseArray<String> names = KEYCODE_SYMBOLIC_NAMES;
- names.append(KEYCODE_UNKNOWN, "KEYCODE_UNKNOWN");
- names.append(KEYCODE_SOFT_LEFT, "KEYCODE_SOFT_LEFT");
- names.append(KEYCODE_SOFT_RIGHT, "KEYCODE_SOFT_RIGHT");
- names.append(KEYCODE_HOME, "KEYCODE_HOME");
- names.append(KEYCODE_BACK, "KEYCODE_BACK");
- names.append(KEYCODE_CALL, "KEYCODE_CALL");
- names.append(KEYCODE_ENDCALL, "KEYCODE_ENDCALL");
- names.append(KEYCODE_0, "KEYCODE_0");
- names.append(KEYCODE_1, "KEYCODE_1");
- names.append(KEYCODE_2, "KEYCODE_2");
- names.append(KEYCODE_3, "KEYCODE_3");
- names.append(KEYCODE_4, "KEYCODE_4");
- names.append(KEYCODE_5, "KEYCODE_5");
- names.append(KEYCODE_6, "KEYCODE_6");
- names.append(KEYCODE_7, "KEYCODE_7");
- names.append(KEYCODE_8, "KEYCODE_8");
- names.append(KEYCODE_9, "KEYCODE_9");
- names.append(KEYCODE_STAR, "KEYCODE_STAR");
- names.append(KEYCODE_POUND, "KEYCODE_POUND");
- names.append(KEYCODE_DPAD_UP, "KEYCODE_DPAD_UP");
- names.append(KEYCODE_DPAD_DOWN, "KEYCODE_DPAD_DOWN");
- names.append(KEYCODE_DPAD_LEFT, "KEYCODE_DPAD_LEFT");
- names.append(KEYCODE_DPAD_RIGHT, "KEYCODE_DPAD_RIGHT");
- names.append(KEYCODE_DPAD_CENTER, "KEYCODE_DPAD_CENTER");
- names.append(KEYCODE_VOLUME_UP, "KEYCODE_VOLUME_UP");
- names.append(KEYCODE_VOLUME_DOWN, "KEYCODE_VOLUME_DOWN");
- names.append(KEYCODE_POWER, "KEYCODE_POWER");
- names.append(KEYCODE_CAMERA, "KEYCODE_CAMERA");
- names.append(KEYCODE_CLEAR, "KEYCODE_CLEAR");
- names.append(KEYCODE_A, "KEYCODE_A");
- names.append(KEYCODE_B, "KEYCODE_B");
- names.append(KEYCODE_C, "KEYCODE_C");
- names.append(KEYCODE_D, "KEYCODE_D");
- names.append(KEYCODE_E, "KEYCODE_E");
- names.append(KEYCODE_F, "KEYCODE_F");
- names.append(KEYCODE_G, "KEYCODE_G");
- names.append(KEYCODE_H, "KEYCODE_H");
- names.append(KEYCODE_I, "KEYCODE_I");
- names.append(KEYCODE_J, "KEYCODE_J");
- names.append(KEYCODE_K, "KEYCODE_K");
- names.append(KEYCODE_L, "KEYCODE_L");
- names.append(KEYCODE_M, "KEYCODE_M");
- names.append(KEYCODE_N, "KEYCODE_N");
- names.append(KEYCODE_O, "KEYCODE_O");
- names.append(KEYCODE_P, "KEYCODE_P");
- names.append(KEYCODE_Q, "KEYCODE_Q");
- names.append(KEYCODE_R, "KEYCODE_R");
- names.append(KEYCODE_S, "KEYCODE_S");
- names.append(KEYCODE_T, "KEYCODE_T");
- names.append(KEYCODE_U, "KEYCODE_U");
- names.append(KEYCODE_V, "KEYCODE_V");
- names.append(KEYCODE_W, "KEYCODE_W");
- names.append(KEYCODE_X, "KEYCODE_X");
- names.append(KEYCODE_Y, "KEYCODE_Y");
- names.append(KEYCODE_Z, "KEYCODE_Z");
- names.append(KEYCODE_COMMA, "KEYCODE_COMMA");
- names.append(KEYCODE_PERIOD, "KEYCODE_PERIOD");
- names.append(KEYCODE_ALT_LEFT, "KEYCODE_ALT_LEFT");
- names.append(KEYCODE_ALT_RIGHT, "KEYCODE_ALT_RIGHT");
- names.append(KEYCODE_SHIFT_LEFT, "KEYCODE_SHIFT_LEFT");
- names.append(KEYCODE_SHIFT_RIGHT, "KEYCODE_SHIFT_RIGHT");
- names.append(KEYCODE_TAB, "KEYCODE_TAB");
- names.append(KEYCODE_SPACE, "KEYCODE_SPACE");
- names.append(KEYCODE_SYM, "KEYCODE_SYM");
- names.append(KEYCODE_EXPLORER, "KEYCODE_EXPLORER");
- names.append(KEYCODE_ENVELOPE, "KEYCODE_ENVELOPE");
- names.append(KEYCODE_ENTER, "KEYCODE_ENTER");
- names.append(KEYCODE_DEL, "KEYCODE_DEL");
- names.append(KEYCODE_GRAVE, "KEYCODE_GRAVE");
- names.append(KEYCODE_MINUS, "KEYCODE_MINUS");
- names.append(KEYCODE_EQUALS, "KEYCODE_EQUALS");
- names.append(KEYCODE_LEFT_BRACKET, "KEYCODE_LEFT_BRACKET");
- names.append(KEYCODE_RIGHT_BRACKET, "KEYCODE_RIGHT_BRACKET");
- names.append(KEYCODE_BACKSLASH, "KEYCODE_BACKSLASH");
- names.append(KEYCODE_SEMICOLON, "KEYCODE_SEMICOLON");
- names.append(KEYCODE_APOSTROPHE, "KEYCODE_APOSTROPHE");
- names.append(KEYCODE_SLASH, "KEYCODE_SLASH");
- names.append(KEYCODE_AT, "KEYCODE_AT");
- names.append(KEYCODE_NUM, "KEYCODE_NUM");
- names.append(KEYCODE_HEADSETHOOK, "KEYCODE_HEADSETHOOK");
- names.append(KEYCODE_FOCUS, "KEYCODE_FOCUS");
- names.append(KEYCODE_PLUS, "KEYCODE_PLUS");
- names.append(KEYCODE_MENU, "KEYCODE_MENU");
- names.append(KEYCODE_NOTIFICATION, "KEYCODE_NOTIFICATION");
- names.append(KEYCODE_SEARCH, "KEYCODE_SEARCH");
- names.append(KEYCODE_MEDIA_PLAY_PAUSE, "KEYCODE_MEDIA_PLAY_PAUSE");
- names.append(KEYCODE_MEDIA_STOP, "KEYCODE_MEDIA_STOP");
- names.append(KEYCODE_MEDIA_NEXT, "KEYCODE_MEDIA_NEXT");
- names.append(KEYCODE_MEDIA_PREVIOUS, "KEYCODE_MEDIA_PREVIOUS");
- names.append(KEYCODE_MEDIA_REWIND, "KEYCODE_MEDIA_REWIND");
- names.append(KEYCODE_MEDIA_FAST_FORWARD, "KEYCODE_MEDIA_FAST_FORWARD");
- names.append(KEYCODE_MUTE, "KEYCODE_MUTE");
- names.append(KEYCODE_PAGE_UP, "KEYCODE_PAGE_UP");
- names.append(KEYCODE_PAGE_DOWN, "KEYCODE_PAGE_DOWN");
- names.append(KEYCODE_PICTSYMBOLS, "KEYCODE_PICTSYMBOLS");
- names.append(KEYCODE_SWITCH_CHARSET, "KEYCODE_SWITCH_CHARSET");
- names.append(KEYCODE_BUTTON_A, "KEYCODE_BUTTON_A");
- names.append(KEYCODE_BUTTON_B, "KEYCODE_BUTTON_B");
- names.append(KEYCODE_BUTTON_C, "KEYCODE_BUTTON_C");
- names.append(KEYCODE_BUTTON_X, "KEYCODE_BUTTON_X");
- names.append(KEYCODE_BUTTON_Y, "KEYCODE_BUTTON_Y");
- names.append(KEYCODE_BUTTON_Z, "KEYCODE_BUTTON_Z");
- names.append(KEYCODE_BUTTON_L1, "KEYCODE_BUTTON_L1");
- names.append(KEYCODE_BUTTON_R1, "KEYCODE_BUTTON_R1");
- names.append(KEYCODE_BUTTON_L2, "KEYCODE_BUTTON_L2");
- names.append(KEYCODE_BUTTON_R2, "KEYCODE_BUTTON_R2");
- names.append(KEYCODE_BUTTON_THUMBL, "KEYCODE_BUTTON_THUMBL");
- names.append(KEYCODE_BUTTON_THUMBR, "KEYCODE_BUTTON_THUMBR");
- names.append(KEYCODE_BUTTON_START, "KEYCODE_BUTTON_START");
- names.append(KEYCODE_BUTTON_SELECT, "KEYCODE_BUTTON_SELECT");
- names.append(KEYCODE_BUTTON_MODE, "KEYCODE_BUTTON_MODE");
- names.append(KEYCODE_ESCAPE, "KEYCODE_ESCAPE");
- names.append(KEYCODE_FORWARD_DEL, "KEYCODE_FORWARD_DEL");
- names.append(KEYCODE_CTRL_LEFT, "KEYCODE_CTRL_LEFT");
- names.append(KEYCODE_CTRL_RIGHT, "KEYCODE_CTRL_RIGHT");
- names.append(KEYCODE_CAPS_LOCK, "KEYCODE_CAPS_LOCK");
- names.append(KEYCODE_SCROLL_LOCK, "KEYCODE_SCROLL_LOCK");
- names.append(KEYCODE_META_LEFT, "KEYCODE_META_LEFT");
- names.append(KEYCODE_META_RIGHT, "KEYCODE_META_RIGHT");
- names.append(KEYCODE_FUNCTION, "KEYCODE_FUNCTION");
- names.append(KEYCODE_SYSRQ, "KEYCODE_SYSRQ");
- names.append(KEYCODE_BREAK, "KEYCODE_BREAK");
- names.append(KEYCODE_MOVE_HOME, "KEYCODE_MOVE_HOME");
- names.append(KEYCODE_MOVE_END, "KEYCODE_MOVE_END");
- names.append(KEYCODE_INSERT, "KEYCODE_INSERT");
- names.append(KEYCODE_FORWARD, "KEYCODE_FORWARD");
- names.append(KEYCODE_MEDIA_PLAY, "KEYCODE_MEDIA_PLAY");
- names.append(KEYCODE_MEDIA_PAUSE, "KEYCODE_MEDIA_PAUSE");
- names.append(KEYCODE_MEDIA_CLOSE, "KEYCODE_MEDIA_CLOSE");
- names.append(KEYCODE_MEDIA_EJECT, "KEYCODE_MEDIA_EJECT");
- names.append(KEYCODE_MEDIA_RECORD, "KEYCODE_MEDIA_RECORD");
- names.append(KEYCODE_F1, "KEYCODE_F1");
- names.append(KEYCODE_F2, "KEYCODE_F2");
- names.append(KEYCODE_F3, "KEYCODE_F3");
- names.append(KEYCODE_F4, "KEYCODE_F4");
- names.append(KEYCODE_F5, "KEYCODE_F5");
- names.append(KEYCODE_F6, "KEYCODE_F6");
- names.append(KEYCODE_F7, "KEYCODE_F7");
- names.append(KEYCODE_F8, "KEYCODE_F8");
- names.append(KEYCODE_F9, "KEYCODE_F9");
- names.append(KEYCODE_F10, "KEYCODE_F10");
- names.append(KEYCODE_F11, "KEYCODE_F11");
- names.append(KEYCODE_F12, "KEYCODE_F12");
- names.append(KEYCODE_NUM_LOCK, "KEYCODE_NUM_LOCK");
- names.append(KEYCODE_NUMPAD_0, "KEYCODE_NUMPAD_0");
- names.append(KEYCODE_NUMPAD_1, "KEYCODE_NUMPAD_1");
- names.append(KEYCODE_NUMPAD_2, "KEYCODE_NUMPAD_2");
- names.append(KEYCODE_NUMPAD_3, "KEYCODE_NUMPAD_3");
- names.append(KEYCODE_NUMPAD_4, "KEYCODE_NUMPAD_4");
- names.append(KEYCODE_NUMPAD_5, "KEYCODE_NUMPAD_5");
- names.append(KEYCODE_NUMPAD_6, "KEYCODE_NUMPAD_6");
- names.append(KEYCODE_NUMPAD_7, "KEYCODE_NUMPAD_7");
- names.append(KEYCODE_NUMPAD_8, "KEYCODE_NUMPAD_8");
- names.append(KEYCODE_NUMPAD_9, "KEYCODE_NUMPAD_9");
- names.append(KEYCODE_NUMPAD_DIVIDE, "KEYCODE_NUMPAD_DIVIDE");
- names.append(KEYCODE_NUMPAD_MULTIPLY, "KEYCODE_NUMPAD_MULTIPLY");
- names.append(KEYCODE_NUMPAD_SUBTRACT, "KEYCODE_NUMPAD_SUBTRACT");
- names.append(KEYCODE_NUMPAD_ADD, "KEYCODE_NUMPAD_ADD");
- names.append(KEYCODE_NUMPAD_DOT, "KEYCODE_NUMPAD_DOT");
- names.append(KEYCODE_NUMPAD_COMMA, "KEYCODE_NUMPAD_COMMA");
- names.append(KEYCODE_NUMPAD_ENTER, "KEYCODE_NUMPAD_ENTER");
- names.append(KEYCODE_NUMPAD_EQUALS, "KEYCODE_NUMPAD_EQUALS");
- names.append(KEYCODE_NUMPAD_LEFT_PAREN, "KEYCODE_NUMPAD_LEFT_PAREN");
- names.append(KEYCODE_NUMPAD_RIGHT_PAREN, "KEYCODE_NUMPAD_RIGHT_PAREN");
- names.append(KEYCODE_VOLUME_MUTE, "KEYCODE_VOLUME_MUTE");
- names.append(KEYCODE_INFO, "KEYCODE_INFO");
- names.append(KEYCODE_CHANNEL_UP, "KEYCODE_CHANNEL_UP");
- names.append(KEYCODE_CHANNEL_DOWN, "KEYCODE_CHANNEL_DOWN");
- names.append(KEYCODE_ZOOM_IN, "KEYCODE_ZOOM_IN");
- names.append(KEYCODE_ZOOM_OUT, "KEYCODE_ZOOM_OUT");
- names.append(KEYCODE_TV, "KEYCODE_TV");
- names.append(KEYCODE_WINDOW, "KEYCODE_WINDOW");
- names.append(KEYCODE_GUIDE, "KEYCODE_GUIDE");
- names.append(KEYCODE_DVR, "KEYCODE_DVR");
- names.append(KEYCODE_BOOKMARK, "KEYCODE_BOOKMARK");
- names.append(KEYCODE_CAPTIONS, "KEYCODE_CAPTIONS");
- names.append(KEYCODE_SETTINGS, "KEYCODE_SETTINGS");
- names.append(KEYCODE_TV_POWER, "KEYCODE_TV_POWER");
- names.append(KEYCODE_TV_INPUT, "KEYCODE_TV_INPUT");
- names.append(KEYCODE_STB_INPUT, "KEYCODE_STB_INPUT");
- names.append(KEYCODE_STB_POWER, "KEYCODE_STB_POWER");
- names.append(KEYCODE_AVR_POWER, "KEYCODE_AVR_POWER");
- names.append(KEYCODE_AVR_INPUT, "KEYCODE_AVR_INPUT");
- names.append(KEYCODE_PROG_RED, "KEYCODE_PROG_RED");
- names.append(KEYCODE_PROG_GREEN, "KEYCODE_PROG_GREEN");
- names.append(KEYCODE_PROG_YELLOW, "KEYCODE_PROG_YELLOW");
- names.append(KEYCODE_PROG_BLUE, "KEYCODE_PROG_BLUE");
- names.append(KEYCODE_APP_SWITCH, "KEYCODE_APP_SWITCH");
- names.append(KEYCODE_BUTTON_1, "KEYCODE_BUTTON_1");
- names.append(KEYCODE_BUTTON_2, "KEYCODE_BUTTON_2");
- names.append(KEYCODE_BUTTON_3, "KEYCODE_BUTTON_3");
- names.append(KEYCODE_BUTTON_4, "KEYCODE_BUTTON_4");
- names.append(KEYCODE_BUTTON_5, "KEYCODE_BUTTON_5");
- names.append(KEYCODE_BUTTON_6, "KEYCODE_BUTTON_6");
- names.append(KEYCODE_BUTTON_7, "KEYCODE_BUTTON_7");
- names.append(KEYCODE_BUTTON_8, "KEYCODE_BUTTON_8");
- names.append(KEYCODE_BUTTON_9, "KEYCODE_BUTTON_9");
- names.append(KEYCODE_BUTTON_10, "KEYCODE_BUTTON_10");
- names.append(KEYCODE_BUTTON_11, "KEYCODE_BUTTON_11");
- names.append(KEYCODE_BUTTON_12, "KEYCODE_BUTTON_12");
- names.append(KEYCODE_BUTTON_13, "KEYCODE_BUTTON_13");
- names.append(KEYCODE_BUTTON_14, "KEYCODE_BUTTON_14");
- names.append(KEYCODE_BUTTON_15, "KEYCODE_BUTTON_15");
- names.append(KEYCODE_BUTTON_16, "KEYCODE_BUTTON_16");
- names.append(KEYCODE_LANGUAGE_SWITCH, "KEYCODE_LANGUAGE_SWITCH");
- names.append(KEYCODE_MANNER_MODE, "KEYCODE_MANNER_MODE");
- names.append(KEYCODE_3D_MODE, "KEYCODE_3D_MODE");
- names.append(KEYCODE_CONTACTS, "KEYCODE_CONTACTS");
- names.append(KEYCODE_CALENDAR, "KEYCODE_CALENDAR");
- names.append(KEYCODE_MUSIC, "KEYCODE_MUSIC");
- names.append(KEYCODE_CALCULATOR, "KEYCODE_CALCULATOR");
- names.append(KEYCODE_ZENKAKU_HANKAKU, "KEYCODE_ZENKAKU_HANKAKU");
- names.append(KEYCODE_EISU, "KEYCODE_EISU");
- names.append(KEYCODE_MUHENKAN, "KEYCODE_MUHENKAN");
- names.append(KEYCODE_HENKAN, "KEYCODE_HENKAN");
- names.append(KEYCODE_KATAKANA_HIRAGANA, "KEYCODE_KATAKANA_HIRAGANA");
- names.append(KEYCODE_YEN, "KEYCODE_YEN");
- names.append(KEYCODE_RO, "KEYCODE_RO");
- names.append(KEYCODE_KANA, "KEYCODE_KANA");
- names.append(KEYCODE_ASSIST, "KEYCODE_ASSIST");
- names.append(KEYCODE_BRIGHTNESS_DOWN, "KEYCODE_BRIGHTNESS_DOWN");
- names.append(KEYCODE_BRIGHTNESS_UP, "KEYCODE_BRIGHTNESS_UP");
- names.append(KEYCODE_MEDIA_AUDIO_TRACK, "KEYCODE_MEDIA_AUDIO_TRACK");
- names.append(KEYCODE_SLEEP, "KEYCODE_SLEEP");
- names.append(KEYCODE_WAKEUP, "KEYCODE_WAKEUP");
- };
-
// Symbolic names of all metakeys in bit order from least significant to most significant.
// Accordingly there are exactly 32 values in this table.
private static final String[] META_SYMBOLIC_NAMES = new String[] {
@@ -927,6 +694,8 @@ public class KeyEvent extends InputEvent implements Parcelable {
"0x80000000",
};
+ private static final String LABEL_PREFIX = "KEYCODE_";
+
/**
* @deprecated There are now more than MAX_KEYCODE keycodes.
* Use {@link #getMaxKeyCode()} instead.
@@ -1178,20 +947,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 +969,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 +978,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 +1057,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 +1087,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 +1103,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 +1114,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,27 +1126,26 @@ 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.
*/
boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
}
- static {
- populateKeycodeSymbolicNames();
- }
+ private static native String nativeKeyCodeToString(int keyCode);
+ private static native int nativeKeyCodeFromString(String keyCode);
private KeyEvent() {
}
/**
* 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 +1159,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 +1182,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 +1207,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 +1236,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 +1267,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 +1303,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 +1341,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 +1460,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 +1470,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 +1490,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 +1515,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 +1551,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
@@ -1793,19 +1561,15 @@ public class KeyEvent extends InputEvent implements Parcelable {
return mAction == ACTION_DOWN;
}
- /**
- * 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
+ /** Is this a system key? System keys can not be used for menu shortcuts.
*/
public final boolean isSystem() {
- return native_isSystemKey(mKeyCode);
+ return isSystemKey(mKeyCode);
}
/** @hide */
- public final boolean hasDefaultAction() {
- return native_hasDefaultAction(mKeyCode);
+ public final boolean isWakeKey() {
+ return isWakeKey(mKeyCode);
}
/**
@@ -1851,16 +1615,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 +1629,83 @@ 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:
+ public static final boolean isMediaKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ return true;
+ }
+ return false;
+ }
+
+
+ /** Is this a system key? System keys can not be used for menu shortcuts.
+ * @hide
+ */
+ public static final boolean isSystemKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MENU:
+ case KeyEvent.KEYCODE_SOFT_RIGHT:
+ case KeyEvent.KEYCODE_HOME:
case KeyEvent.KEYCODE_BACK:
+ case KeyEvent.KEYCODE_CALL:
+ case KeyEvent.KEYCODE_ENDCALL:
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_POWER:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ 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:
+ case KeyEvent.KEYCODE_CAMERA:
+ case KeyEvent.KEYCODE_FOCUS:
+ case KeyEvent.KEYCODE_SEARCH:
+ case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
+ case KeyEvent.KEYCODE_BRIGHTNESS_UP:
+ case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ return true;
+ }
+
+ return false;
+ }
+
+ /** @hide */
+ public static final boolean isWakeKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BACK:
+ case KeyEvent.KEYCODE_POWER:
+ case KeyEvent.KEYCODE_MENU:
+ case KeyEvent.KEYCODE_SLEEP:
+ case KeyEvent.KEYCODE_WAKEUP:
return true;
- default:
- return false;
}
+ return false;
}
/** {@inheritDoc} */
@@ -2352,7 +2177,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 +2191,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 +2202,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 +2211,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 +2219,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 +2234,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 +2258,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 +2272,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 +2284,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 +2313,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Renamed to {@link #getDeviceId}.
- *
+ *
* @hide
* @deprecated use {@link #getDeviceId()} instead.
*/
@@ -2520,7 +2345,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 +2368,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 +2392,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 +2407,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 +2422,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 +2435,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 +2459,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 +2468,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 +2476,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 +2551,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
int mDownKeyCode;
Object mDownTarget;
SparseIntArray mActiveLongPresses = new SparseIntArray();
-
+
/**
* Reset back to initial state.
*/
@@ -2736,7 +2561,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
mDownTarget = null;
mActiveLongPresses.clear();
}
-
+
/**
* Stop any tracking associated with this target.
*/
@@ -2747,14 +2572,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 +2592,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 +2600,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 +2610,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.
@@ -2862,8 +2687,8 @@ public class KeyEvent extends InputEvent implements Parcelable {
* @see KeyCharacterMap#getDisplayLabel
*/
public static String keyCodeToString(int keyCode) {
- String symbolicName = KEYCODE_SYMBOLIC_NAMES.get(keyCode);
- return symbolicName != null ? symbolicName : Integer.toString(keyCode);
+ String symbolicName = nativeKeyCodeToString(keyCode);
+ return symbolicName != null ? LABEL_PREFIX + symbolicName : Integer.toString(keyCode);
}
/**
@@ -2875,17 +2700,13 @@ public class KeyEvent extends InputEvent implements Parcelable {
* @see #keycodeToString(int)
*/
public static int keyCodeFromString(String symbolicName) {
- if (symbolicName == null) {
- throw new IllegalArgumentException("symbolicName must not be null");
+ if (symbolicName.startsWith(LABEL_PREFIX)) {
+ symbolicName = symbolicName.substring(LABEL_PREFIX.length());
}
-
- final int count = KEYCODE_SYMBOLIC_NAMES.size();
- for (int i = 0; i < count; i++) {
- if (symbolicName.equals(KEYCODE_SYMBOLIC_NAMES.valueAt(i))) {
- return i;
- }
+ int keyCode = nativeKeyCodeFromString(symbolicName);
+ if (keyCode > 0) {
+ return keyCode;
}
-
try {
return Integer.parseInt(symbolicName, 10);
} catch (NumberFormatException ex) {
@@ -2940,12 +2761,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();
@@ -2973,7 +2794,4 @@ public class KeyEvent extends InputEvent implements Parcelable {
out.writeLong(mDownTime);
out.writeLong(mEventTime);
}
-
- private native boolean native_isSystemKey(int keyCode);
- private native boolean native_hasDefaultAction(int keyCode);
}
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index cd905fa..b9ed801 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -21,13 +21,16 @@ import android.os.Handler;
import android.os.Message;
import android.os.Trace;
import android.widget.FrameLayout;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.Context;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.Xml;
import java.io.IOException;
@@ -61,7 +64,8 @@ import java.util.HashMap;
* @see Context#getSystemService
*/
public abstract class LayoutInflater {
- private final boolean DEBUG = false;
+ private static final String TAG = LayoutInflater.class.getSimpleName();
+ private static final boolean DEBUG = false;
/**
* This field should be made private, so it is hidden from the SDK.
@@ -90,6 +94,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
@@ -391,8 +399,13 @@ public abstract class LayoutInflater {
* the inflated XML file.
*/
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
- if (DEBUG) System.out.println("INFLATING from resource: " + resource);
- XmlResourceParser parser = getContext().getResources().getLayout(resource);
+ final Resources res = getContext().getResources();
+ if (DEBUG) {
+ Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ + Integer.toHexString(resource) + ")");
+ }
+
+ final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
@@ -459,15 +472,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 +497,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 +678,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 +766,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 +789,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 +810,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 +825,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 +883,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 +909,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/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 6378ffd..0626ab9 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -167,6 +167,7 @@ import android.util.SparseArray;
*/
public final class MotionEvent extends InputEvent implements Parcelable {
private static final long NS_PER_MS = 1000000;
+ private static final String LABEL_PREFIX = "AXIS_";
/**
* An invalid pointer id.
@@ -1369,6 +1370,9 @@ public final class MotionEvent extends InputEvent implements Parcelable {
private static native long nativeReadFromParcel(long nativePtr, Parcel parcel);
private static native void nativeWriteToParcel(long nativePtr, Parcel parcel);
+ private static native String nativeAxisToString(int axis);
+ private static native int nativeAxisFromString(String label);
+
private MotionEvent() {
}
@@ -3051,8 +3055,8 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* @return The symbolic name of the specified axis.
*/
public static String axisToString(int axis) {
- String symbolicName = AXIS_SYMBOLIC_NAMES.get(axis);
- return symbolicName != null ? symbolicName : Integer.toString(axis);
+ String symbolicName = nativeAxisToString(axis);
+ return symbolicName != null ? LABEL_PREFIX + symbolicName : Integer.toString(axis);
}
/**
@@ -3064,17 +3068,13 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* @see KeyEvent#keyCodeToString(int)
*/
public static int axisFromString(String symbolicName) {
- if (symbolicName == null) {
- throw new IllegalArgumentException("symbolicName must not be null");
+ if (symbolicName.startsWith(LABEL_PREFIX)) {
+ symbolicName = symbolicName.substring(LABEL_PREFIX.length());
}
-
- final int count = AXIS_SYMBOLIC_NAMES.size();
- for (int i = 0; i < count; i++) {
- if (symbolicName.equals(AXIS_SYMBOLIC_NAMES.valueAt(i))) {
- return i;
- }
+ int axis = nativeAxisFromString(symbolicName);
+ if (axis >= 0) {
+ return axis;
}
-
try {
return Integer.parseInt(symbolicName, 10);
} catch (NumberFormatException ex) {
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..8b80c3e0
--- /dev/null
+++ b/core/java/android/view/RenderNode.java
@@ -0,0 +1,961 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <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;
+
+ // We need to keep a strong reference to all running animators to ensure that
+ // they can call removeAnimator when they have finished, as the native-side
+ // object can only hold a WeakReference<> to avoid leaking memory due to
+ // cyclic references.
+ private List<RenderNodeAnimator> mActiveAnimators;
+
+ private RenderNode(String name) {
+ mNativeRenderNode = nCreate(name);
+ }
+
+ /**
+ * @see RenderNode#adopt(long)
+ */
+ private RenderNode(long nativePtr) {
+ mNativeRenderNode = nativePtr;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Adopts an existing native render node.
+ *
+ * Note: This will *NOT* incRef() on the native object, however it will
+ * decRef() when it is destroyed. The caller should have already incRef'd it
+ */
+ public static RenderNode adopt(long nativePtr) {
+ return new RenderNode(nativePtr);
+ }
+
+
+ /**
+ * 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);
+ }
+
+ public void setElevation(float lift) {
+ nSetElevation(mNativeRenderNode, lift);
+ }
+
+ public float getElevation() {
+ return nGetElevation(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);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Animations
+ ///////////////////////////////////////////////////////////////////////////
+
+ public void addAnimator(RenderNodeAnimator animator) {
+ if (mActiveAnimators == null) {
+ mActiveAnimators = new ArrayList<RenderNodeAnimator>();
+ }
+ mActiveAnimators.add(animator);
+ nAddAnimator(mNativeRenderNode, animator.getNativeAnimator());
+ }
+
+ public void removeAnimator(RenderNodeAnimator animator) {
+ nRemoveAnimator(mNativeRenderNode, animator.getNativeAnimator());
+ mActiveAnimators.remove(animator);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // 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 nSetElevation(long renderNode, float lift);
+ 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 nGetElevation(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);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Animations
+ ///////////////////////////////////////////////////////////////////////////
+
+ private static native void nAddAnimator(long renderNode, long animatorPtr);
+ private static native void nRemoveAnimator(long renderNode, long animatorPtr);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Finalization
+ ///////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nDestroyRenderNode(mNativeRenderNode);
+ } finally {
+ super.finalize();
+ }
+ }
+}
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
new file mode 100644
index 0000000..b70ae3d
--- /dev/null
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -0,0 +1,122 @@
+/*
+ * 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.util.SparseIntArray;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * @hide
+ */
+public final class RenderNodeAnimator {
+
+ // Keep in sync with enum RenderProperty in Animator.h
+ private static final int TRANSLATION_X = 0;
+ private static final int TRANSLATION_Y = 1;
+ private static final int TRANSLATION_Z = 2;
+ private static final int SCALE_X = 3;
+ private static final int SCALE_Y = 4;
+ private static final int ROTATION = 5;
+ private static final int ROTATION_X = 6;
+ private static final int ROTATION_Y = 7;
+ private static final int X = 8;
+ private static final int Y = 9;
+ private static final int Z = 10;
+ private static final int ALPHA = 11;
+
+ // ViewPropertyAnimator uses a mask for its values, we need to remap them
+ // to the enum values here. RenderPropertyAnimator can't use the mask values
+ // directly as internally it uses a lookup table so it needs the values to
+ // be sequential starting from 0
+ private static final SparseIntArray sViewPropertyAnimatorMap = new SparseIntArray(15) {{
+ put(ViewPropertyAnimator.TRANSLATION_X, TRANSLATION_X);
+ put(ViewPropertyAnimator.TRANSLATION_Y, TRANSLATION_Y);
+ put(ViewPropertyAnimator.TRANSLATION_Z, TRANSLATION_Z);
+ put(ViewPropertyAnimator.SCALE_X, SCALE_X);
+ put(ViewPropertyAnimator.SCALE_Y, SCALE_Y);
+ put(ViewPropertyAnimator.ROTATION, ROTATION);
+ put(ViewPropertyAnimator.ROTATION_X, ROTATION_X);
+ put(ViewPropertyAnimator.ROTATION_Y, ROTATION_Y);
+ put(ViewPropertyAnimator.X, X);
+ put(ViewPropertyAnimator.Y, Y);
+ put(ViewPropertyAnimator.Z, Z);
+ put(ViewPropertyAnimator.ALPHA, ALPHA);
+ }};
+
+ // Keep in sync DeltaValueType in Animator.h
+ private static final int DELTA_TYPE_ABSOLUTE = 0;
+ private static final int DELTA_TYPE_DELTA = 1;
+
+ private RenderNode mTarget;
+ private long mNativePtr;
+
+ public int mapViewPropertyToRenderProperty(int viewProperty) {
+ return sViewPropertyAnimatorMap.get(viewProperty);
+ }
+
+ public RenderNodeAnimator(int property, int deltaType, float deltaValue) {
+ mNativePtr = nCreateAnimator(new WeakReference<RenderNodeAnimator>(this),
+ property, deltaType, deltaValue);
+ }
+
+ public void start(View target) {
+ mTarget = target.mRenderNode;
+ mTarget.addAnimator(this);
+ // Kick off a frame to start the process
+ target.invalidateViewProperty(true, false);
+ }
+
+ public void cancel() {
+ mTarget.removeAnimator(this);
+ }
+
+ public void setDuration(int duration) {
+ nSetDuration(mNativePtr, duration);
+ }
+
+ long getNativeAnimator() {
+ return mNativePtr;
+ }
+
+ private void onFinished() {
+ mTarget.removeAnimator(this);
+ }
+
+ // Called by native
+ private static void callOnFinished(WeakReference<RenderNodeAnimator> weakThis) {
+ RenderNodeAnimator animator = weakThis.get();
+ if (animator != null) {
+ animator.onFinished();
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nUnref(mNativePtr);
+ mNativePtr = 0;
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private static native long nCreateAnimator(WeakReference<RenderNodeAnimator> weakThis,
+ int property, int deltaValueType, float deltaValue);
+ private static native void nSetDuration(long nativePtr, int duration);
+ private static native void nUnref(long nativePtr);
+}
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..4a2cc1a 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -188,8 +188,13 @@ public class SurfaceView extends View {
init();
}
- public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
init();
}
@@ -250,8 +255,9 @@ public class SurfaceView extends View {
updateWindow(false, false);
}
+ /** @hide */
@Override
- protected void onDetachedFromWindow() {
+ protected void onDetachedFromWindowInternal() {
if (mGlobalListenersAdded) {
ViewTreeObserver observer = getViewTreeObserver();
observer.removeOnScrollChangedListener(mScrollChangedListener);
@@ -273,7 +279,7 @@ public class SurfaceView extends View {
mSession = null;
mLayout.token = null;
- super.onDetachedFromWindow();
+ super.onDetachedFromWindowInternal();
}
@Override
@@ -416,7 +422,8 @@ public class SurfaceView extends View {
mWindowType = type;
}
- private void updateWindow(boolean force, boolean redrawNeeded) {
+ /** @hide */
+ protected void updateWindow(boolean force, boolean redrawNeeded) {
if (!mHaveFrame) {
return;
}
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index b78af2e..3cfe5e9 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -156,14 +156,32 @@ public class TextureView extends View {
*
* @param context The context to associate this view with.
* @param attrs The attributes of the XML tag that is inflating the view.
- * @param defStyle The default style to apply to this view. If 0, no style
- * will be applied (beyond what is included in the theme). This may
- * either be an attribute resource, whose value will be retrieved
- * from the current theme, or an explicit style resource.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
*/
@SuppressWarnings({"UnusedDeclaration"})
- public TextureView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public TextureView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ /**
+ * Creates a new TextureView.
+ *
+ * @param context The context to associate this view with.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
+ public TextureView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
init();
}
@@ -210,29 +228,16 @@ public class TextureView extends View {
}
}
+ /** @hide */
@Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mLayer != null) {
- boolean success = executeHardwareAction(new Runnable() {
- @Override
- public void run() {
- destroySurface();
- }
- });
-
- if (!success) {
- Log.w(LOG_TAG, "TextureView was not able to destroy its surface: " + this);
- }
- }
+ protected void onDetachedFromWindowInternal() {
+ destroySurface();
+ super.onDetachedFromWindowInternal();
}
private void destroySurface() {
if (mLayer != null) {
- mSurface.detachFromGLContext();
- // SurfaceTexture owns the texture name and detachFromGLContext
- // should have deleted it
- mLayer.clearStorage();
+ mLayer.detachSurfaceTexture(mSurface);
boolean shouldRelease = true;
if (mListener != null) {
@@ -357,7 +362,7 @@ public class TextureView extends View {
return null;
}
- mLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(mOpaque);
+ mLayer = mAttachInfo.mHardwareRenderer.createTextureLayer();
if (!mUpdateSurface) {
// Create a new SurfaceTexture for the layer.
mSurface = mAttachInfo.mHardwareRenderer.createSurfaceTexture(mLayer);
@@ -398,7 +403,7 @@ public class TextureView extends View {
updateLayer();
mMatrixChanged = true;
- mAttachInfo.mHardwareRenderer.setSurfaceTexture(mLayer, mSurface);
+ mLayer.setSurfaceTexture(mSurface);
mSurface.setDefaultBufferSize(getWidth(), getHeight());
}
@@ -451,7 +456,8 @@ public class TextureView extends View {
}
}
- mLayer.update(getWidth(), getHeight(), mOpaque);
+ mLayer.prepare(getWidth(), getHeight(), mOpaque);
+ mLayer.updateSurfaceTexture();
if (mListener != null) {
mListener.onSurfaceTextureUpdated(mSurface);
@@ -589,14 +595,6 @@ public class TextureView extends View {
*/
public Bitmap getBitmap(Bitmap bitmap) {
if (bitmap != null && isAvailable()) {
- AttachInfo info = mAttachInfo;
- if (info != null && info.mHardwareRenderer != null &&
- info.mHardwareRenderer.isEnabled()) {
- if (!info.mHardwareRenderer.validate()) {
- throw new IllegalStateException("Could not acquire hardware rendering context");
- }
- }
-
applyUpdate();
applyTransformMatrix();
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
new file mode 100644
index 0000000..eaec8ab
--- /dev/null
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -0,0 +1,319 @@
+/*
+ * 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();
+
+ private int mWidth, mHeight;
+ private long mNativeProxy;
+ private boolean mInitialized = false;
+ private RenderNode mRootNode;
+
+ ThreadedRenderer(boolean translucent) {
+ long rootNodePtr = nCreateRootRenderNode();
+ mRootNode = RenderNode.adopt(rootNodePtr);
+ mRootNode.setClipToBounds(false);
+ mNativeProxy = nCreateProxy(translucent, rootNodePtr);
+ }
+
+ @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);
+ try {
+ callbacks.onHardwarePostDraw(canvas);
+ canvas.drawDisplayList(view.getDisplayList());
+ callbacks.onHardwarePostDraw(canvas);
+ } finally {
+ 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);
+
+ attachInfo.mIgnoreDirtyState = false;
+
+ if (dirty == null) {
+ dirty = NULL_RECT;
+ }
+ nSyncAndDrawFrame(mNativeProxy, 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
+ 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 nCreateRootRenderNode();
+ private static native long nCreateProxy(boolean translucent, long rootRenderNode);
+ 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 nSyncAndDrawFrame(long nativeProxy,
+ 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..6afff4d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,18 +16,23 @@
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;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
-import android.graphics.Camera;
import android.graphics.Canvas;
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 +91,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 +102,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 +667,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 +676,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
@@ -708,6 +719,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static boolean sIgnoreMeasureCache = false;
/**
+ * Ignore the clipBounds of this view for the children.
+ */
+ static boolean sIgnoreClipBoundsForChildren = false;
+
+ /**
* This view does not want keystrokes. Use with TAKES_FOCUS_MASK when
* calling setFlags.
*/
@@ -729,6 +745,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 +917,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 +966,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 +1056,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 +1077,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 +1654,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 +1796,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 +1868,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 +2065,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 +2330,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/* End of masks for mPrivateFlags2 */
- /* Masks for mPrivateFlags3 */
+ /**
+ * Masks for mPrivateFlags3, as generated by dumpFlags():
+ *
+ * |-------|-------|-------|-------|
+ * 1 PFLAG3_VIEW_IS_ANIMATING_TRANSFORM
+ * 1 PFLAG3_VIEW_IS_ANIMATING_ALPHA
+ * 1 PFLAG3_IS_LAID_OUT
+ * 1 PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT
+ * 1 PFLAG3_CALLED_SUPER
+ * |-------|-------|-------|-------|
+ */
/**
* Flag indicating that view has a transform animation set on it. This is used to track whether
@@ -2268,6 +2374,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
static final int PFLAG3_CALLED_SUPER = 0x10;
+ /**
+ * Flag indicating that a view's outline has been specifically defined.
+ */
+ static final int PFLAG3_OUTLINE_DEFINED = 0x20;
/**
* Flag indicating that we're in the process of applying window insets.
@@ -2279,6 +2389,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x80;
+ /**
+ * Flag indicating that nested scrolling is enabled for this view.
+ * The view will optionally cooperate with views up its parent chain to allow for
+ * integrated nested scrolling along the same axis.
+ */
+ static final int PFLAG3_NESTED_SCROLLING_ENABLED = 0x200;
+
/* End of masks for mPrivateFlags3 */
static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED;
@@ -2674,6 +2791,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 +2818,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)
*
@@ -2725,6 +2848,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public static final int SCREEN_STATE_ON = 0x1;
/**
+ * Indicates no axis of view scrolling.
+ */
+ public static final int SCROLL_AXIS_NONE = 0;
+
+ /**
+ * Indicates scrolling along the horizontal axis.
+ */
+ public static final int SCROLL_AXIS_HORIZONTAL = 1 << 0;
+
+ /**
+ * Indicates scrolling along the vertical axis.
+ */
+ public static final int SCROLL_AXIS_VERTICAL = 1 << 1;
+
+ /**
* Controls the over-scroll mode for this view.
* See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)},
* {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS},
@@ -2810,120 +2948,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.
*/
@@ -2943,17 +2984,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Current clip bounds. to which all drawing of this view are constrained.
*/
- private Rect mClipBounds = null;
+ Rect mClipBounds = null;
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 +3170,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 +3252,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private int[] mDrawableState = null;
/**
+ * Stores the outline of the view, passed down to the DisplayList level for
+ * defining shadow shape.
+ */
+ 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 +3460,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
@@ -3430,6 +3487,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
ViewOverlay mOverlay;
/**
+ * The currently active parent view for receiving delegated nested scrolling events.
+ * This is set by {@link #startNestedScroll(int)} during a touch interaction and cleared
+ * by {@link #stopNestedScroll()} at the same point where we clear
+ * requestDisallowInterceptTouchEvent.
+ */
+ private ViewParent mNestedScrollingParent;
+
+ /**
* Consistency verifier for debugging purposes.
* @hide
*/
@@ -3439,6 +3504,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
+ private int[] mTempNestedScrollConsumed;
+
/**
* Simple constructor to use when creating a view from code.
*
@@ -3461,6 +3528,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;
@@ -3472,6 +3540,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// of whether a layout was requested on that View.
sIgnoreMeasureCache = targetSdkVersion < KITKAT;
+ // Older apps may need this to ignore the clip bounds
+ sIgnoreClipBoundsForChildren = targetSdkVersion < L;
+
sCompatibilityDone = true;
}
}
@@ -3497,27 +3568,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 +3648,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
float tx = 0;
float ty = 0;
+ float tz = 0;
+ float elevation = 0;
float rotation = 0;
float rotationX = 0;
float rotationY = 0;
@@ -3619,6 +3729,14 @@ 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_elevation:
+ elevation = a.getDimensionPixelOffset(attr, 0);
+ transformSet = true;
+ break;
case com.android.internal.R.styleable.View_rotation:
rotation = a.getFloat(attr, 0);
transformSet = true;
@@ -3871,6 +3989,12 @@ 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;
+ case R.styleable.View_nestedScrollingEnabled:
+ setNestedScrollingEnabled(a.getBoolean(attr, false));
+ break;
}
}
@@ -3960,6 +4084,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (transformSet) {
setTranslationX(tx);
setTranslationY(ty);
+ setTranslationZ(tz);
+ setElevation(elevation);
setRotation(rotation);
setRotationX(rotationX);
setRotationY(rotationY);
@@ -3979,6 +4105,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
View() {
mResources = null;
+ mRenderNode = RenderNode.create(getClass().getName());
}
public String toString() {
@@ -4026,7 +4153,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 +4735,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 +4753,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 (!focused && v != null) {
+ v.getBoundsOnScreen(r);
+ final int[] location = new int[2];
+ getLocationOnScreen(location);
+ r.offset(-location[0], -location[1]);
+ } else {
+ r.set(0, 0, mRight - mLeft, mBottom - mTop);
+ }
+
+ 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 +4875,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
System.out.println(this + " clearFocus()");
}
- clearFocusInternal(true, true);
+ clearFocusInternal(null, true, true);
}
/**
@@ -4729,7 +4887,7 @@ 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;
@@ -4739,6 +4897,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
onFocusChanged(false, 0, null);
+ manageFocusHotspot(false, focused);
refreshDrawableState();
if (propagate && (!refocus || !rootViewRequestFocus())) {
@@ -4766,12 +4925,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 +4978,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 +5041,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 +5658,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 +5836,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 +5854,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 +6071,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 +6088,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 +6313,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 +6325,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 +6484,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 +6507,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 +6537,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 +6907,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 +6926,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 +6938,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 +6988,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 +7002,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 +7022,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 +7052,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 +7100,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 +7447,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 +7531,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 +7545,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 +7567,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 +7586,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 +7904,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
public void dispatchStartTemporaryDetach() {
- clearDisplayList();
-
onStartTemporaryDetach();
}
@@ -7825,27 +8009,46 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
+ boolean result = false;
+
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
+ final int actionMasked = event.getActionMasked();
+ if (actionMasked == MotionEvent.ACTION_DOWN) {
+ // Defensive cleanup for new gesture
+ stopNestedScroll();
+ }
+
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
- if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
+ if (li != null && li.mOnTouchListener != null
+ && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
- return true;
+ result = true;
}
- if (onTouchEvent(event)) {
- return true;
+ if (!result && onTouchEvent(event)) {
+ result = true;
}
}
- if (mInputEventConsistencyVerifier != null) {
+ if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
- return false;
+
+ // Clean up after nested scrolls if this is the end of a gesture;
+ // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
+ // of the gesture.
+ if (actionMasked == MotionEvent.ACTION_UP ||
+ actionMasked == MotionEvent.ACTION_CANCEL ||
+ (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
+ stopNestedScroll();
+ }
+
+ return result;
}
/**
@@ -8080,7 +8283,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 +8295,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 +8314,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 +8327,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 +8338,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 +8352,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 +8364,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 +8525,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 +8567,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 +8645,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 +8841,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 +8963,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 +9002,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 +9035,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 +9059,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 +9093,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 +9151,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 +9314,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 +9326,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 +9630,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 +9643,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 +9652,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 +9660,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 +9677,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 +9721,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 +9741,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 +9759,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 +9780,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 +9803,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 +9823,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 +9846,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 +9867,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 +9881,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 +9902,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 +9916,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 +9939,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 +9958,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 +9981,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 +9999,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 +10082,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 +10107,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 +10128,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 +10175,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 +10198,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 +10243,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 +10263,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 +10299,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 +10322,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 +10358,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 +10378,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 +10404,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 +10427,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();
}
/**
@@ -10394,6 +10441,48 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
setTranslationY(y - mTop);
}
+ /**
+ * The visual z position of this view, in pixels. This is equivalent to the
+ * {@link #setTranslationZ(float) translationZ} property plus the current
+ * {@link #getElevation() elevation} property.
+ *
+ * @return The visual z position of this view, in pixels.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getZ() {
+ return getElevation() + getTranslationZ();
+ }
+
+ /**
+ * Sets the visual z position of this view, in pixels. This is equivalent to setting the
+ * {@link #setTranslationZ(float) translationZ} property to be the difference between
+ * the x value passed in and the current {@link #getElevation() elevation} property.
+ *
+ * @param z The visual z position of this view, in pixels.
+ */
+ public void setZ(float z) {
+ setTranslationZ(z - getElevation());
+ }
+
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getElevation() {
+ return mRenderNode.getElevation();
+ }
+
+ /**
+ * Sets the base depth location of this view.
+ *
+ * @attr ref android.R.styleable#View_elevation
+ */
+ public void setElevation(float elevation) {
+ if (elevation != getElevation()) {
+ invalidateViewProperty(true, false);
+ mRenderNode.setElevation(elevation);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ }
+ }
/**
* The horizontal location of this view relative to its {@link #getLeft() left} position.
@@ -10404,7 +10493,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 +10507,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 +10526,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 +10540,154 @@ 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);
+ mRenderNode.setTranslationY(translationY);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ }
+ }
+
+ /**
+ * The depth location of this view relative to its {@link #getElevation() elevation}.
+ *
+ * @return The depth of this view relative to its elevation.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getTranslationZ() {
+ return mRenderNode.getTranslationZ();
+ }
+
+ /**
+ * Sets the depth location of this view relative to its {@link #getElevation() elevation}.
+ *
+ * @attr ref android.R.styleable#View_translationZ
+ */
+ public void setTranslationZ(float translationZ) {
+ if (translationZ != getTranslationZ()) {
invalidateViewProperty(true, false);
- info.mTranslationY = translationY;
- info.mMatrixDirty = true;
+ mRenderNode.setTranslationZ(translationZ);
invalidateViewProperty(false, true);
- if (mDisplayList != null) {
- mDisplayList.setTranslationY(translationY);
+
+ 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.
+ * <p>
+ * If the outline is not set or is null, shadows will be cast from the
+ * bounds of the View.
+ *
+ * @param outline The new outline of the view.
+ * Must be {@link android.graphics.Outline#isValid() valid.}
+ */
+ 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);
+ }
+
+ // TODO: remove
+ public final boolean getClipToOutline() { return false; }
+ public void setClipToOutline(boolean clipToOutline) {}
+
+ private void queryOutlineFromBackgroundIfUndefined() {
+ if ((mPrivateFlags3 & PFLAG3_OUTLINE_DEFINED) == 0) {
+ // Outline not currently defined, query from background
+ if (mOutline == null) {
+ mOutline = new Outline();
+ } else {
+ //invalidate outline, to ensure background calculates it
+ mOutline.set(null);
+ }
+ if (mBackground.getOutline(mOutline)) {
+ if (!mOutline.isValid()) {
+ throw new IllegalStateException("Background drawable failed to build outline");
+ }
+ mRenderNode.setOutline(mOutline);
+ } else {
+ mRenderNode.setOutline(null);
}
}
}
/**
+ * 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 +10776,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 +10806,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 +10825,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 +10852,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 +11124,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 +11179,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() && getZ() != 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 +11296,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 +11307,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 && getZ() != 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 +11379,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 +11462,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 +11585,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 +12078,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 +12102,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 +12494,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
InputMethodManager imm = InputMethodManager.peekInstance();
imm.focusIn(this);
}
-
- if (mDisplayList != null) {
- mDisplayList.clearDirty();
- }
}
/**
@@ -12365,7 +12600,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,13 +12794,31 @@ 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;
+ if (mBackground != null) {
+ mBackground.clearHotspots();
+ }
+
removeUnsetPressCallback();
removeLongPressCallback();
removePerformClickCallback();
removeSendViewScrolledAccessibilityEventCallback();
+ stopNestedScroll();
destroyDrawingCache();
destroyLayer(false);
@@ -12576,15 +12829,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 +12999,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
onDetachedFromWindow();
+ onDetachedFromWindowInternal();
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
@@ -13053,11 +13301,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 +13358,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 +13418,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 +13447,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 +13456,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 +13467,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 +13498,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 +13617,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 +13655,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 +13704,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 +14327,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 +14349,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 +14362,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 +14411,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 +14451,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 +14743,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 +14910,88 @@ 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;
+ queryOutlineFromBackgroundIfUndefined();
+ }
+
+ // 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);
+ try {
+ drawable.draw(canvas);
+ } finally {
+ 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 +15260,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,14 +15324,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @param drawable the drawable to invalidate
*/
- public void invalidateDrawable(Drawable drawable) {
+ @Override
+ public void invalidateDrawable(@NonNull Drawable drawable) {
if (verifyDrawable(drawable)) {
- final Rect dirty = drawable.getBounds();
+ final Rect dirty = drawable.getDirtyBounds();
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
+
+ if (drawable == mBackground) {
+ queryOutlineFromBackgroundIfUndefined();
+ }
}
}
@@ -15113,6 +15348,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 +15368,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 +15438,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @hide
*/
- public void onResolveDrawables(int layoutDirection) {
+ public void onResolveDrawables(@ResolvedLayoutDir int layoutDirection) {
}
/**
@@ -15249,7 +15485,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 +15670,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 +16205,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 +16226,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 +16562,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 +16593,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()
@@ -17739,6 +17985,269 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Enable or disable nested scrolling for this view.
+ *
+ * <p>If this property is set to true the view will be permitted to initiate nested
+ * scrolling operations with a compatible parent view in the current hierarchy. If this
+ * view does not implement nested scrolling this will have no effect. Disabling nested scrolling
+ * while a nested scroll is in progress has the effect of {@link #stopNestedScroll() stopping}
+ * the nested scroll.</p>
+ *
+ * @param enabled true to enable nested scrolling, false to disable
+ *
+ * @see #isNestedScrollingEnabled()
+ */
+ public void setNestedScrollingEnabled(boolean enabled) {
+ if (enabled) {
+ mPrivateFlags3 |= PFLAG3_NESTED_SCROLLING_ENABLED;
+ } else {
+ stopNestedScroll();
+ mPrivateFlags3 &= ~PFLAG3_NESTED_SCROLLING_ENABLED;
+ }
+ }
+
+ /**
+ * Returns true if nested scrolling is enabled for this view.
+ *
+ * <p>If nested scrolling is enabled and this View class implementation supports it,
+ * this view will act as a nested scrolling child view when applicable, forwarding data
+ * about the scroll operation in progress to a compatible and cooperating nested scrolling
+ * parent.</p>
+ *
+ * @return true if nested scrolling is enabled
+ *
+ * @see #setNestedScrollingEnabled(boolean)
+ */
+ public boolean isNestedScrollingEnabled() {
+ return (mPrivateFlags3 & PFLAG3_NESTED_SCROLLING_ENABLED) ==
+ PFLAG3_NESTED_SCROLLING_ENABLED;
+ }
+
+ /**
+ * Begin a nestable scroll operation along the given axes.
+ *
+ * <p>A view starting a nested scroll promises to abide by the following contract:</p>
+ *
+ * <p>The view will call startNestedScroll upon initiating a scroll operation. In the case
+ * of a touch scroll this corresponds to the initial {@link MotionEvent#ACTION_DOWN}.
+ * In the case of touch scrolling the nested scroll will be terminated automatically in
+ * the same manner as {@link ViewParent#requestDisallowInterceptTouchEvent(boolean)}.
+ * In the event of programmatic scrolling the caller must explicitly call
+ * {@link #stopNestedScroll()} to indicate the end of the nested scroll.</p>
+ *
+ * <p>If <code>startNestedScroll</code> returns true, a cooperative parent was found.
+ * If it returns false the caller may ignore the rest of this contract until the next scroll.
+ * Calling startNestedScroll while a nested scroll is already in progress will return true.</p>
+ *
+ * <p>At each incremental step of the scroll the caller should invoke
+ * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll}
+ * once it has calculated the requested scrolling delta. If it returns true the nested scrolling
+ * parent at least partially consumed the scroll and the caller should adjust the amount it
+ * scrolls by.</p>
+ *
+ * <p>After applying the remainder of the scroll delta the caller should invoke
+ * {@link #dispatchNestedScroll(int, int, int, int, int[]) dispatchNestedScroll}, passing
+ * both the delta consumed and the delta unconsumed. A nested scrolling parent may treat
+ * these values differently. See {@link ViewParent#onNestedScroll(View, int, int, int, int)}.
+ * </p>
+ *
+ * @param axes Flags consisting of a combination of {@link #SCROLL_AXIS_HORIZONTAL} and/or
+ * {@link #SCROLL_AXIS_VERTICAL}.
+ * @return true if a cooperative parent was found and nested scrolling has been enabled for
+ * the current gesture.
+ *
+ * @see #stopNestedScroll()
+ * @see #dispatchNestedPreScroll(int, int, int[], int[])
+ * @see #dispatchNestedScroll(int, int, int, int, int[])
+ */
+ public boolean startNestedScroll(int axes) {
+ if (hasNestedScrollingParent()) {
+ // Already in progress
+ return true;
+ }
+ if (isNestedScrollingEnabled()) {
+ ViewParent p = getParent();
+ View child = this;
+ while (p != null) {
+ try {
+ if (p.onStartNestedScroll(child, this, axes)) {
+ mNestedScrollingParent = p;
+ p.onNestedScrollAccepted(child, this, axes);
+ return true;
+ }
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, "ViewParent " + p + " does not implement interface " +
+ "method onStartNestedScroll", e);
+ // Allow the search upward to continue
+ }
+ if (p instanceof View) {
+ child = (View) p;
+ }
+ p = p.getParent();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Stop a nested scroll in progress.
+ *
+ * <p>Calling this method when a nested scroll is not currently in progress is harmless.</p>
+ *
+ * @see #startNestedScroll(int)
+ */
+ public void stopNestedScroll() {
+ if (mNestedScrollingParent != null) {
+ mNestedScrollingParent.onStopNestedScroll(this);
+ mNestedScrollingParent = null;
+ }
+ }
+
+ /**
+ * Returns true if this view has a nested scrolling parent.
+ *
+ * <p>The presence of a nested scrolling parent indicates that this view has initiated
+ * a nested scroll and it was accepted by an ancestor view further up the view hierarchy.</p>
+ *
+ * @return whether this view has a nested scrolling parent
+ */
+ public boolean hasNestedScrollingParent() {
+ return mNestedScrollingParent != null;
+ }
+
+ /**
+ * Dispatch one step of a nested scroll in progress.
+ *
+ * <p>Implementations of views that support nested scrolling should call this to report
+ * info about a scroll in progress to the current nested scrolling parent. If a nested scroll
+ * is not currently in progress or nested scrolling is not
+ * {@link #isNestedScrollingEnabled() enabled} for this view this method does nothing.</p>
+ *
+ * <p>Compatible View implementations should also call
+ * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll} before
+ * consuming a component of the scroll event themselves.</p>
+ *
+ * @param dxConsumed Horizontal distance in pixels consumed by this view during this scroll step
+ * @param dyConsumed Vertical distance in pixels consumed by this view during this scroll step
+ * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by this view
+ * @param dyUnconsumed Horizontal scroll distance in pixels not consumed by this view
+ * @param offsetInWindow Optional. If not null, on return this will contain the offset
+ * in local view coordinates of this view from before this operation
+ * to after it completes. View implementations may use this to adjust
+ * expected input coordinate tracking.
+ * @return true if the event was dispatched, false if it could not be dispatched.
+ * @see #dispatchNestedPreScroll(int, int, int[], int[])
+ */
+ public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
+ if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+ if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
+ int startX = 0;
+ int startY = 0;
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ startX = offsetInWindow[0];
+ startY = offsetInWindow[1];
+ }
+
+ mNestedScrollingParent.onNestedScroll(this, dxConsumed, dyConsumed,
+ dxUnconsumed, dyUnconsumed);
+
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ offsetInWindow[0] -= startX;
+ offsetInWindow[1] -= startY;
+ }
+ return true;
+ } else if (offsetInWindow != null) {
+ // No motion, no dispatch. Keep offsetInWindow up to date.
+ offsetInWindow[0] = 0;
+ offsetInWindow[1] = 0;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Dispatch one step of a nested scroll in progress before this view consumes any portion of it.
+ *
+ * <p>Nested pre-scroll events are to nested scroll events what touch intercept is to touch.
+ * <code>dispatchNestedPreScroll</code> offers an opportunity for the parent view in a nested
+ * scrolling operation to consume some or all of the scroll operation before the child view
+ * consumes it.</p>
+ *
+ * @param dx Horizontal scroll distance in pixels
+ * @param dy Vertical scroll distance in pixels
+ * @param consumed Output. If not null, consumed[0] will contain the consumed component of dx
+ * and consumed[1] the consumed dy.
+ * @param offsetInWindow Optional. If not null, on return this will contain the offset
+ * in local view coordinates of this view from before this operation
+ * to after it completes. View implementations may use this to adjust
+ * expected input coordinate tracking.
+ * @return true if the parent consumed some or all of the scroll delta
+ * @see #dispatchNestedScroll(int, int, int, int, int[])
+ */
+ public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
+ if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+ if (dx != 0 || dy != 0) {
+ int startX = 0;
+ int startY = 0;
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ startX = offsetInWindow[0];
+ startY = offsetInWindow[1];
+ }
+
+ if (consumed == null) {
+ if (mTempNestedScrollConsumed == null) {
+ mTempNestedScrollConsumed = new int[2];
+ }
+ consumed = mTempNestedScrollConsumed;
+ }
+ consumed[0] = 0;
+ consumed[1] = 0;
+ mNestedScrollingParent.onNestedPreScroll(this, dx, dy, consumed);
+
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ offsetInWindow[0] -= startX;
+ offsetInWindow[1] -= startY;
+ }
+ return consumed[0] != 0 || consumed[1] != 0;
+ } else if (offsetInWindow != null) {
+ offsetInWindow[0] = 0;
+ offsetInWindow[1] = 0;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Dispatch a fling to a nested scrolling parent.
+ *
+ * <p>This method should be used to indicate that a nested scrolling child has detected
+ * suitable conditions for a fling. Generally this means that a touch scroll has ended with a
+ * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+ * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+ * along a scrollable axis.</p>
+ *
+ * <p>If a nested scrolling child view would normally fling but it is at the edge of
+ * its own content, it can use this method to delegate the fling to its nested scrolling
+ * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
+ *
+ * @param velocityX Horizontal fling velocity in pixels per second
+ * @param velocityY Vertical fling velocity in pixels per second
+ * @param consumed true if the child consumed the fling, false otherwise
+ * @return true if the nested scrolling parent consumed or otherwise reacted to the fling
+ */
+ public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
+ if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+ return mNestedScrollingParent.onNestedFling(this, velocityX, velocityY, consumed);
+ }
+ return false;
+ }
+
+ /**
* Gets a scale factor that determines the distance the view should scroll
* vertically in response to {@link MotionEvent#ACTION_SCROLL}.
* @return The vertical scroll scale factor.
@@ -18020,6 +18529,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 +18553,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 +18594,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 +18757,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 +18836,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.
*/
@@ -18330,6 +18884,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
};
/**
+ * A Property wrapper around the <code>z</code> functionality handled by the
+ * {@link View#setZ(float)} and {@link View#getZ()} methods.
+ */
+ public static final Property<View, Float> Z = new FloatProperty<View>("z") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setZ(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getZ();
+ }
+ };
+
+ /**
* A Property wrapper around the <code>rotation</code> functionality handled by the
* {@link View#setRotation(float)} and {@link View#getRotation()} methods.
*/
@@ -18552,10 +19122,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 +19141,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 +19179,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 +19433,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 +19528,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final Callbacks mRootCallbacks;
- HardwareCanvas mHardwareCanvas;
-
IWindowId mIWindowId;
WindowId mWindowId;
@@ -18932,7 +19537,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
View mRootView;
IBinder mPanelParentWindowToken;
- Surface mSurface;
boolean mHardwareAccelerated;
boolean mHardwareAccelerationRequested;
@@ -19177,7 +19781,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 4b8541e..20ef429 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..43bc0b6 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.
@@ -455,21 +460,29 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@ViewDebug.ExportedProperty(category = "layout")
private int mChildCountWithTransientState = 0;
+ /**
+ * Currently registered axes for nested scrolling. Flag set consisting of
+ * {@link #SCROLL_AXIS_HORIZONTAL} {@link #SCROLL_AXIS_VERTICAL} or {@link #SCROLL_AXIS_NONE}
+ * for null.
+ */
+ private int mNestedScrollAxes;
+
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 +512,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 +560,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 +615,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 +633,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 +829,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;
}
}
@@ -2000,6 +2018,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
+ mNestedScrollAxes = SCROLL_AXIS_NONE;
}
/**
@@ -2277,6 +2296,41 @@ 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
+ * Activity Transitions.
+ * @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.
+ * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window,
+ * android.app.ActivityOptions.ActivityTransitionListener)
+ */
+ 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 +2563,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 +2637,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (int i = 0; i < count; i++) {
children[i].dispatchDetachedFromWindow();
}
+ clearDisappearingChildren();
super.dispatchDetachedFromWindow();
}
@@ -2915,14 +2970,24 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
- int saveCount = 0;
+ int clipSaveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
+ boolean hasClipBounds = mClipBounds != null && !sIgnoreClipBoundsForChildren;
+ boolean clippingNeeded = clipToPadding || hasClipBounds;
+
+ if (clippingNeeded) {
+ clipSaveCount = canvas.save();
+ }
+
if (clipToPadding) {
- saveCount = canvas.save();
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
+ }
+ if (hasClipBounds) {
+ canvas.clipRect(mClipBounds.left, mClipBounds.top, mClipBounds.right,
+ mClipBounds.bottom);
}
// We will draw our child's animation, let's reset the flag
@@ -2963,8 +3028,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
onDebugDraw(canvas);
}
- if (clipToPadding) {
- canvas.restoreToCount(saveCount);
+ if (clippingNeeded) {
+ canvas.restoreToCount(clipSaveCount);
}
// mGroupFlags might have been updated by drawChild()
@@ -3103,7 +3168,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 +3193,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 +3683,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 +3891,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 +3926,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 +3986,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
if (view == focused) {
- view.unFocus();
+ view.unFocus(null);
clearChildFocus = true;
}
@@ -4008,7 +4073,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
if (view == focused) {
- view.unFocus();
+ view.unFocus(null);
clearChildFocus = true;
}
@@ -4404,7 +4469,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 +4492,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 +4514,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 +4629,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 +5282,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();
}
}
@@ -5790,10 +5864,109 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return true;
}
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+ return false;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int axes) {
+ mNestedScrollAxes = axes;
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * <p>The default implementation of onStopNestedScroll calls
+ * {@link #stopNestedScroll()} to halt any recursive nested scrolling in progress.</p>
+ */
+ @Override
+ public void onStopNestedScroll(View child) {
+ // Stop any recursive nested scrolling.
+ stopNestedScroll();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed) {
+ // Do nothing
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+ // Do nothing
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+ return false;
+ }
+
+ /**
+ * Return the current axes of nested scrolling for this ViewGroup.
+ *
+ * <p>A ViewGroup returning something other than {@link #SCROLL_AXIS_NONE} is currently
+ * acting as a nested scrolling parent for one or more descendant views in the hierarchy.</p>
+ *
+ * @return Flags indicating the current axes of nested scrolling
+ * @see #SCROLL_AXIS_HORIZONTAL
+ * @see #SCROLL_AXIS_VERTICAL
+ * @see #SCROLL_AXIS_NONE
+ */
+ public int getNestedScrollAxes() {
+ return mNestedScrollAxes;
+ }
+
/** @hide */
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/ViewParent.java b/core/java/android/view/ViewParent.java
index 0137693..588b9cd 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -407,4 +407,126 @@ public interface ViewParent {
* {@link View#TEXT_ALIGNMENT_VIEW_END}
*/
public int getTextAlignment();
+
+ /**
+ * React to a descendant view initiating a nestable scroll operation, claiming the
+ * nested scroll operation if appropriate.
+ *
+ * <p>This method will be called in response to a descendant view invoking
+ * {@link View#startNestedScroll(int)}. Each parent up the view hierarchy will be
+ * given an opportunity to respond and claim the nested scrolling operation by returning
+ * <code>true</code>.</p>
+ *
+ * <p>This method may be overridden by ViewParent implementations to indicate when the view
+ * is willing to support a nested scrolling operation that is about to begin. If it returns
+ * true, this ViewParent will become the target view's nested scrolling parent for the duration
+ * of the scroll operation in progress. When the nested scroll is finished this ViewParent
+ * will receive a call to {@link #onStopNestedScroll(View)}.
+ * </p>
+ *
+ * @param child Direct child of this ViewParent containing target
+ * @param target View that initiated the nested scroll
+ * @param nestedScrollAxes Flags consisting of {@link View#SCROLL_AXIS_HORIZONTAL},
+ * {@link View#SCROLL_AXIS_VERTICAL} or both
+ * @return true if this ViewParent accepts the nested scroll operation
+ */
+ public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
+
+ /**
+ * React to the successful claiming of a nested scroll operation.
+ *
+ * <p>This method will be called after
+ * {@link #onStartNestedScroll(View, View, int) onStartNestedScroll} returns true. It offers
+ * an opportunity for the view and its superclasses to perform initial configuration
+ * for the nested scroll. Implementations of this method should always call their superclass's
+ * implementation of this method if one is present.</p>
+ *
+ * @param child Direct child of this ViewParent containing target
+ * @param target View that initiated the nested scroll
+ * @param nestedScrollAxes Flags consisting of {@link View#SCROLL_AXIS_HORIZONTAL},
+ * {@link View#SCROLL_AXIS_VERTICAL} or both
+ * @see #onStartNestedScroll(View, View, int)
+ * @see #onStopNestedScroll(View)
+ */
+ public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
+
+ /**
+ * React to a nested scroll operation ending.
+ *
+ * <p>Perform cleanup after a nested scrolling operation.
+ * This method will be called when a nested scroll stops, for example when a nested touch
+ * scroll ends with a {@link MotionEvent#ACTION_UP} or {@link MotionEvent#ACTION_CANCEL} event.
+ * Implementations of this method should always call their superclass's implementation of this
+ * method if one is present.</p>
+ *
+ * @param target View that initiated the nested scroll
+ */
+ public void onStopNestedScroll(View target);
+
+ /**
+ * React to a nested scroll in progress.
+ *
+ * <p>This method will be called when the ViewParent's current nested scrolling child view
+ * dispatches a nested scroll event. To receive calls to this method the ViewParent must have
+ * previously returned <code>true</code> for a call to
+ * {@link #onStartNestedScroll(View, View, int)}.</p>
+ *
+ * <p>Both the consumed and unconsumed portions of the scroll distance are reported to the
+ * ViewParent. An implementation may choose to use the consumed portion to match or chase scroll
+ * position of multiple child elements, for example. The unconsumed portion may be used to
+ * allow continuous dragging of multiple scrolling or draggable elements, such as scrolling
+ * a list within a vertical drawer where the drawer begins dragging once the edge of inner
+ * scrolling content is reached.</p>
+ *
+ * @param target The descendent view controlling the nested scroll
+ * @param dxConsumed Horizontal scroll distance in pixels already consumed by target
+ * @param dyConsumed Vertical scroll distance in pixels already consumed by target
+ * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by target
+ * @param dyUnconsumed Vertical scroll distance in pixels not consumed by target
+ */
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed);
+
+ /**
+ * React to a nested scroll in progress before the target view consumes a portion of the scroll.
+ *
+ * <p>When working with nested scrolling often the parent view may want an opportunity
+ * to consume the scroll before the nested scrolling child does. An example of this is a
+ * drawer that contains a scrollable list. The user will want to be able to scroll the list
+ * fully into view before the list itself begins scrolling.</p>
+ *
+ * <p><code>onNestedPreScroll</code> is called when a nested scrolling child invokes
+ * {@link View#dispatchNestedPreScroll(int, int, int[], int[])}. The implementation should
+ * report how any pixels of the scroll reported by dx, dy were consumed in the
+ * <code>consumed</code> array. Index 0 corresponds to dx and index 1 corresponds to dy.
+ * This parameter will never be null. Initial values for consumed[0] and consumed[1]
+ * will always be 0.</p>
+ *
+ * @param target View that initiated the nested scroll
+ * @param dx Horizontal scroll distance in pixels
+ * @param dy Vertical scroll distance in pixels
+ * @param consumed Output. The horizontal and vertical scroll distance consumed by this parent
+ */
+ public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
+
+ /**
+ * Request a fling from a nested scroll.
+ *
+ * <p>This method signifies that a nested scrolling child has detected suitable conditions
+ * for a fling. Generally this means that a touch scroll has ended with a
+ * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+ * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+ * along a scrollable axis.</p>
+ *
+ * <p>If a nested scrolling child view would normally fling but it is at the edge of
+ * its own content, it can use this method to delegate the fling to its nested scrolling
+ * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
+ *
+ * @param target View that initiated the nested scroll
+ * @param velocityX Horizontal velocity in pixels per second.
+ * @param velocityY Vertical velocity in pixels per second
+ * @param consumed true if the child consumed the fling, false otherwise
+ * @return true if this parent consumed or otherwise reacted to the fling
+ */
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
}
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 67a94be..11d2622 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -133,20 +133,22 @@ public class ViewPropertyAnimator {
* Constants used to associate a property being requested and the mechanism used to set
* the property (this class calls directly into View to set the properties in question).
*/
- 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;
+ static final int NONE = 0x0000;
+ static final int TRANSLATION_X = 0x0001;
+ static final int TRANSLATION_Y = 0x0002;
+ static final int TRANSLATION_Z = 0x0004;
+ static final int SCALE_X = 0x0008;
+ static final int SCALE_Y = 0x0010;
+ static final int ROTATION = 0x0020;
+ static final int ROTATION_X = 0x0040;
+ static final int ROTATION_Y = 0x0080;
+ static final int X = 0x0100;
+ static final int Y = 0x0200;
+ static final int Z = 0x0400;
+ static final int ALPHA = 0x0800;
+
+ private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | TRANSLATION_Z |
+ SCALE_X | SCALE_Y | ROTATION | ROTATION_X | ROTATION_Y | X | Y | Z;
/**
* The mechanism by which the user can request several properties that are then animated
@@ -469,6 +471,32 @@ public class ViewPropertyAnimator {
}
/**
+ * This method will cause the View's <code>z</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#setZ(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator z(float value) {
+ animateProperty(Z, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>z</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#setZ(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator zBy(float value) {
+ animatePropertyBy(Z, value);
+ return this;
+ }
+
+ /**
* This method will cause the View's <code>rotation</code> property to be animated to the
* specified value. Animations already running on the property will be canceled.
*
@@ -599,6 +627,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 +952,44 @@ 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 Z:
+ renderNode.setTranslationZ(value - renderNode.getElevation());
break;
case ALPHA:
info.mAlpha = value;
- if (displayList != null) displayList.setAlpha(value);
+ renderNode.setAlpha(value);
break;
}
}
@@ -951,28 +1001,32 @@ 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 Z:
+ return node.getElevation() + node.getTranslationZ();
case ALPHA:
- return info.mAlpha;
+ return mView.mTransformationInfo.mAlpha;
}
return 0;
}
@@ -1061,7 +1115,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 +1123,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 +1145,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 1cb0473..db87394 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
@@ -234,6 +234,7 @@ public final class ViewRootImpl implements ViewParent,
InputStage mFirstInputStage;
InputStage mFirstPostImeInputStage;
+ InputStage mSyntheticInputStage;
boolean mWindowAttributesChanged = false;
int mWindowAttributesChangesFlag = 0;
@@ -270,6 +271,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 +298,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)}
*/
@@ -597,8 +600,8 @@ public final class ViewRootImpl implements ViewParent,
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
- InputStage syntheticInputStage = new SyntheticInputStage();
- InputStage viewPostImeStage = new ViewPostImeInputStage(syntheticInputStage);
+ mSyntheticInputStage = new SyntheticInputStage();
+ InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
@@ -621,7 +624,6 @@ public final class ViewRootImpl implements ViewParent,
}
void destroyHardwareResources() {
- invalidateDisplayLists();
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.destroyHardwareResources(mView);
mAttachInfo.mHardwareRenderer.destroy(false);
@@ -635,23 +637,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,20 +665,28 @@ 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);
}
}
+ public boolean invokeFunctor(long functor, boolean waitForCompletion) {
+ if (mAttachInfo.mHardwareRenderer == null || !mAttachInfo.mHardwareRenderer.isEnabled()) {
+ return false;
+ }
+ mAttachInfo.mHardwareRenderer.invokeFunctor(functor, waitForCompletion);
+ return true;
+ }
+
private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
mAttachInfo.mHardwareAccelerated = false;
mAttachInfo.mHardwareAccelerationRequested = false;
@@ -720,7 +732,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 +972,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 +1158,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);
+ }
+
void dispatchApplyInsets(View host) {
mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets);
boolean isRound = false;
@@ -1234,11 +1263,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
@@ -1447,6 +1471,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 &&
@@ -1481,24 +1511,19 @@ 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;
+ if (mResizeBuffer == null) {
+ mResizeBuffer = mAttachInfo.mHardwareRenderer.createDisplayListLayer(
+ mWidth, mHeight);
+ }
+ mResizeBuffer.prepare(mWidth, mHeight, false);
+ RenderNode layerRenderNode = mResizeBuffer.startRecording();
+ HardwareCanvas layerCanvas = layerRenderNode.start(mWidth, mHeight);
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;
@@ -1516,10 +1541,10 @@ public final class ViewRootImpl implements ViewParent,
mTranslator.translateCanvas(layerCanvas);
}
- DisplayList displayList = mView.mDisplayList;
- if (displayList != null && displayList.isValid()) {
- layerCanvas.drawDisplayList(displayList, null,
- DisplayList.FLAG_CLIP_CHILDREN);
+ RenderNode renderNode = mView.mRenderNode;
+ if (renderNode != null && renderNode.isValid()) {
+ layerCanvas.drawDisplayList(renderNode, null,
+ RenderNode.FLAG_CLIP_CHILDREN);
} else {
mView.draw(layerCanvas);
}
@@ -1529,19 +1554,16 @@ public final class ViewRootImpl implements ViewParent,
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);
+ layerRenderNode.end(layerCanvas);
+ layerRenderNode.setCaching(true);
+ layerRenderNode.setLeftTopRightBottom(0, 0, mWidth, mHeight);
+ mTempRect.set(0, 0, mWidth, mHeight);
} finally {
- if (mResizeBuffer != null) {
- mResizeBuffer.end(hwRendererCanvas);
- if (!completed) {
- disposeResizeBuffer();
- }
- }
+ mResizeBuffer.endRecording(mTempRect);
}
+ mAttachInfo.mHardwareRenderer.flushLayerUpdates();
}
mAttachInfo.mContentInsets.set(mPendingContentInsets);
if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
@@ -1584,7 +1606,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;
@@ -1611,7 +1633,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;
@@ -1694,7 +1716,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;
}
}
@@ -2211,11 +2233,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();
}
}
@@ -2294,6 +2314,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());
@@ -2400,8 +2423,6 @@ public final class ViewRootImpl implements ViewParent,
appScale + ", width=" + mWidth + ", height=" + mHeight);
}
- invalidateDisplayLists();
-
attachInfo.mTreeObserver.dispatchOnDraw();
if (!dirty.isEmpty() || mIsAnimating) {
@@ -2414,6 +2435,7 @@ public final class ViewRootImpl implements ViewParent,
mCurrentDirty.set(dirty);
dirty.setEmpty();
+ mBlockResizeBuffer = false;
attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
animating ? null : mCurrentDirty);
} else {
@@ -2431,7 +2453,7 @@ public final class ViewRootImpl implements ViewParent,
try {
attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
- mHolder.getSurface());
+ mSurface);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
@@ -2566,28 +2588,31 @@ 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);
+ 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);
@@ -2603,7 +2628,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;
@@ -2611,20 +2636,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
*/
@@ -2866,10 +2877,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();
}
@@ -2886,7 +2893,6 @@ public final class ViewRootImpl implements ViewParent,
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
- mAttachInfo.mSurface = null;
mSurface.release();
@@ -2951,7 +2957,7 @@ public final class ViewRootImpl implements ViewParent,
}
}
}
-
+
/**
* Return true if child is an ancestor of parent, (or equal to the parent).
*/
@@ -2998,6 +3004,7 @@ public final class ViewRootImpl implements ViewParent,
private final static int MSG_INVALIDATE_WORLD = 23;
private final static int MSG_WINDOW_MOVED = 24;
private final static int MSG_FLUSH_LAYER_UPDATES = 25;
+ private final static int MSG_SYNTHESIZE_INPUT_EVENT = 26;
final class ViewRootHandler extends Handler {
@Override
@@ -3047,6 +3054,8 @@ public final class ViewRootImpl implements ViewParent,
return "MSG_WINDOW_MOVED";
case MSG_FLUSH_LAYER_UPDATES:
return "MSG_FLUSH_LAYER_UPDATES";
+ case MSG_SYNTHESIZE_INPUT_EVENT:
+ return "MSG_SYNTHESIZE_INPUT_EVENT";
}
return super.getMessageName(message);
}
@@ -3142,7 +3151,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 {
@@ -3191,8 +3200,6 @@ public final class ViewRootImpl implements ViewParent,
mHasHadWindowFocus = true;
}
- setAccessibilityFocus(null, null);
-
if (mView != null && mAccessibilityManager.isEnabled()) {
if (hasWindowFocus) {
mView.sendAccessibilityEvent(
@@ -3205,8 +3212,15 @@ public final class ViewRootImpl implements ViewParent,
doDie();
break;
case MSG_DISPATCH_INPUT_EVENT: {
+ SomeArgs args = (SomeArgs)msg.obj;
+ InputEvent event = (InputEvent)args.arg1;
+ InputEventReceiver receiver = (InputEventReceiver)args.arg2;
+ enqueueInputEvent(event, receiver, 0, true);
+ args.recycle();
+ } break;
+ case MSG_SYNTHESIZE_INPUT_EVENT: {
InputEvent event = (InputEvent)msg.obj;
- enqueueInputEvent(event, null, 0, true);
+ enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true);
} break;
case MSG_DISPATCH_KEY_FROM_IME: {
if (LOCAL_LOGV) Log.v(
@@ -3217,7 +3231,8 @@ public final class ViewRootImpl implements ViewParent,
// The IME is trying to say this event is from the
// system! Bad bad bad!
//noinspection UnusedAssignment
- event = KeyEvent.changeFlags(event, event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM);
+ event = KeyEvent.changeFlags(event, event.getFlags() &
+ ~KeyEvent.FLAG_FROM_SYSTEM);
}
enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
} break;
@@ -3335,7 +3350,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;
}
}
@@ -4013,6 +4028,7 @@ public final class ViewRootImpl implements ViewParent,
private final SyntheticJoystickHandler mJoystick = new SyntheticJoystickHandler();
private final SyntheticTouchNavigationHandler mTouchNavigation =
new SyntheticTouchNavigationHandler();
+ private final SyntheticKeyboardHandler mKeyboard = new SyntheticKeyboardHandler();
public SyntheticInputStage() {
super(null);
@@ -4035,7 +4051,11 @@ public final class ViewRootImpl implements ViewParent,
mTouchNavigation.process(event);
return FINISH_HANDLED;
}
+ } else if ((q.mFlags & QueuedInputEvent.FLAG_UNHANDLED) != 0) {
+ mKeyboard.process((KeyEvent)q.mEvent);
+ return FINISH_HANDLED;
}
+
return FORWARD;
}
@@ -4540,8 +4560,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;
@@ -4569,9 +4588,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);
}
@@ -4638,7 +4654,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;
@@ -4877,6 +4892,33 @@ public final class ViewRootImpl implements ViewParent,
};
}
+ final class SyntheticKeyboardHandler {
+ public void process(KeyEvent event) {
+ if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) {
+ return;
+ }
+
+ final KeyCharacterMap kcm = event.getKeyCharacterMap();
+ final int keyCode = event.getKeyCode();
+ final int metaState = event.getMetaState();
+
+ // Check for fallback actions specified by the key character map.
+ KeyCharacterMap.FallbackAction fallbackAction =
+ kcm.getFallbackAction(keyCode, metaState);
+ if (fallbackAction != null) {
+ final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
+ KeyEvent fallbackEvent = KeyEvent.obtain(
+ event.getDownTime(), event.getEventTime(),
+ event.getAction(), fallbackAction.keyCode,
+ event.getRepeatCount(), fallbackAction.metaState,
+ event.getDeviceId(), event.getScanCode(),
+ flags, event.getSource(), null);
+ fallbackAction.recycle();
+ enqueueInputEvent(fallbackEvent);
+ }
+ }
+ }
+
/**
* Returns true if the key is used for keyboard navigation.
* @param keyEvent The key event.
@@ -5268,10 +5310,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) {
@@ -5319,7 +5361,6 @@ public final class ViewRootImpl implements ViewParent,
}
if (mAdded && !mFirst) {
- invalidateDisplayLists();
destroyHardwareRenderer();
if (mView != null) {
@@ -5365,7 +5406,7 @@ public final class ViewRootImpl implements ViewParent,
// Hardware rendering
if (mAttachInfo.mHardwareRenderer != null) {
- if (mAttachInfo.mHardwareRenderer.loadSystemProperties(mHolder.getSurface())) {
+ if (mAttachInfo.mHardwareRenderer.loadSystemProperties()) {
invalidate();
}
}
@@ -5457,6 +5498,7 @@ public final class ViewRootImpl implements ViewParent,
public static final int FLAG_FINISHED = 1 << 2;
public static final int FLAG_FINISHED_HANDLED = 1 << 3;
public static final int FLAG_RESYNTHESIZED = 1 << 4;
+ public static final int FLAG_UNHANDLED = 1 << 5;
public QueuedInputEvent mNext;
@@ -5471,6 +5513,14 @@ public final class ViewRootImpl implements ViewParent,
return mEvent instanceof MotionEvent
&& mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER);
}
+
+ public boolean shouldSendToSynthesizer() {
+ if ((mFlags & FLAG_UNHANDLED) != 0) {
+ return true;
+ }
+
+ return false;
+ }
}
private QueuedInputEvent obtainQueuedInputEvent(InputEvent event,
@@ -5568,24 +5618,29 @@ 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;
+ if (q.shouldSendToSynthesizer()) {
+ stage = mSyntheticInputStage;
+ } else {
+ 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);
@@ -5786,10 +5841,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
@@ -5799,7 +5850,20 @@ public final class ViewRootImpl implements ViewParent,
}
public void dispatchInputEvent(InputEvent event) {
- Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, event);
+ dispatchInputEvent(event, null);
+ }
+
+ public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = event;
+ args.arg2 = receiver;
+ Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ }
+
+ public void synthesizeInputEvent(InputEvent event) {
+ Message msg = mHandler.obtainMessage(MSG_SYNTHESIZE_INPUT_EVENT, event);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
@@ -5810,28 +5874,17 @@ public final class ViewRootImpl implements ViewParent,
mHandler.sendMessage(msg);
}
- public void dispatchUnhandledKey(KeyEvent event) {
- if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
- final KeyCharacterMap kcm = event.getKeyCharacterMap();
- final int keyCode = event.getKeyCode();
- final int metaState = event.getMetaState();
-
- // Check for fallback actions specified by the key character map.
- KeyCharacterMap.FallbackAction fallbackAction =
- kcm.getFallbackAction(keyCode, metaState);
- if (fallbackAction != null) {
- final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
- KeyEvent fallbackEvent = KeyEvent.obtain(
- event.getDownTime(), event.getEventTime(),
- event.getAction(), fallbackAction.keyCode,
- event.getRepeatCount(), fallbackAction.metaState,
- event.getDeviceId(), event.getScanCode(),
- flags, event.getSource(), null);
- fallbackAction.recycle();
-
- dispatchInputEvent(fallbackEvent);
- }
+ /**
+ * Reinject unhandled {@link InputEvent}s in order to synthesize fallbacks events.
+ *
+ * Note that it is the responsibility of the caller of this API to recycle the InputEvent it
+ * passes in.
+ */
+ public void dispatchUnhandledInputEvent(InputEvent event) {
+ if (event instanceof MotionEvent) {
+ event = MotionEvent.obtain((MotionEvent) event);
}
+ synthesizeInputEvent(event);
}
public void dispatchAppVisibility(boolean visible) {
@@ -6093,6 +6146,33 @@ public final class ViewRootImpl implements ViewParent,
// Do nothing.
}
+ @Override
+ public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+ return false;
+ }
+
+ @Override
+ public void onStopNestedScroll(View target) {
+ }
+
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
+ }
+
+ @Override
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed) {
+ }
+
+ @Override
+ public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+ }
+
+ @Override
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+ return false;
+ }
+
void changeCanvasOpacity(boolean opaque) {
// TODO(romainguy): recreate Canvas (software or hardware) to reflect the opacity change.
Log.d(TAG, "changeCanvasOpacity: opaque=" + opaque);
@@ -6298,68 +6378,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) {
@@ -6469,7 +6487,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,
@@ -6480,9 +6498,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..9c44bd1 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,6 +27,9 @@ 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;
/**
@@ -89,17 +94,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 +258,7 @@ public abstract class Window {
*
* @see #onPreparePanel
*/
+ @Nullable
public View onCreatePanelView(int featureId);
/**
@@ -373,6 +387,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 +995,7 @@ public abstract class Window {
*
* @return View The current View with focus or null.
*/
+ @Nullable
public abstract View getCurrentFocus();
/**
@@ -988,10 +1004,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 +1050,7 @@ public abstract class Window {
*/
public void setBackgroundDrawableResource(int resid)
{
- setBackgroundDrawable(mContext.getResources().getDrawable(resid));
+ setBackgroundDrawable(mContext.getDrawable(resid));
}
/**
@@ -1328,4 +1346,169 @@ 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;
+ }
+
+ /**
+ * Sets the Transition that will be used to move Views into the initial scene. The entering
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#INVISIBLE} to {@link View#VISIBLE}. If <code>transition</code> is null,
+ * entering Views will remain unaffected.
+ * @param transition The Transition to use to move Views into the initial Scene.
+ */
+ public void setEnterTransition(Transition transition) {}
+
+ /**
+ * Sets the Transition that will be used to move Views out of the scene when starting a
+ * new Activity. The exiting Views will be those that are regular Views or ViewGroups that
+ * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as exiting is governed by changing visibility
+ * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will
+ * remain unaffected. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ * @param transition The Transition to use to move Views out of the scene when calling a
+ * new Activity.
+ */
+ public void setExitTransition(Transition transition) {}
+
+ /**
+ * Returns the transition used to move Views into the initial scene. The entering
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#INVISIBLE} to {@link View#VISIBLE}. If <code>transition</code> is null,
+ * entering Views will remain unaffected. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * @return the Transition to use to move Views into the initial Scene.
+ */
+ public Transition getEnterTransition() { return null; }
+
+ /**
+ * Returns the Transition that will be used to move Views out of the scene when starting a
+ * new Activity. The exiting Views will be those that are regular Views or ViewGroups that
+ * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as exiting is governed by changing visibility
+ * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will
+ * remain unaffected. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ * @return the Transition to use to move Views out of the scene when calling a
+ * new Activity.
+ */
+ public Transition getExitTransition() { return null; }
+
+ /**
+ * Sets the Transition that will be used for shared elements transferred into the content
+ * Scene. Typical Transitions will affect size and location, such as
+ * {@link android.transition.MoveImage} and {@link android.transition.ChangeBounds}. A null
+ * value will cause transferred shared elements to blink to the final position.
+ * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ * @param transition The Transition to use for shared elements transferred into the content
+ * Scene.
+ */
+ public void setSharedElementEnterTransition(Transition transition) {}
+
+ /**
+ * Returns the Transition that will be used for shared elements transferred into the content
+ * Scene. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ * @return Transition to use for sharend elements transferred into the content Scene.
+ */
+ public Transition getSharedElementEnterTransition() { return null; }
+
+ /**
+ * Sets the Transition that will be used for shared elements after starting a new Activity
+ * before the shared elements are transferred to the called Activity. If the shared elements
+ * must animate during the exit transition, this Transition should be used. Upon completion,
+ * the shared elements may be transferred to the started Activity.
+ * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ * @param transition The Transition to use for shared elements in the launching Window
+ * prior to transferring to the launched Activity's Window.
+ */
+ public void setSharedElementExitTransition(Transition transition) {}
+
+ /**
+ * Returns the Transition to use for shared elements in the launching Window prior
+ * to transferring to the launched Activity's Window.
+ * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * @return the Transition to use for shared elements in the launching Window prior
+ * to transferring to the launched Activity's Window.
+ */
+ public Transition getSharedElementExitTransition() { return null; }
+
+ /**
+ * Controls how the transition set in
+ * {@link #setEnterTransition(android.transition.Transition)} overlaps with the exit
+ * transition of the calling Activity. When true, the transition will start as soon as possible.
+ * When false, the transition will wait until the remote exiting transition completes before
+ * starting.
+ * @param allow true to start the enter transition when possible or false to
+ * wait until the exiting transition completes.
+ */
+ public void setAllowEnterTransitionOverlap(boolean allow) {}
+
+ /**
+ * Returns how the transition set in
+ * {@link #setEnterTransition(android.transition.Transition)} overlaps with the exit
+ * transition of the calling Activity. When true, the transition will start as soon as possible.
+ * When false, the transition will wait until the remote exiting transition completes before
+ * starting.
+ * @return true when the enter transition should start as soon as possible or false to
+ * when it should wait until the exiting transition completes.
+ */
+ public boolean getAllowEnterTransitionOverlap() { return true; }
+
+ /**
+ * Controls how the transition set in
+ * {@link #setExitTransition(android.transition.Transition)} overlaps with the exit
+ * transition of the called Activity when reentering after if finishes. When true,
+ * the transition will start as soon as possible. When false, the transition will wait
+ * until the called Activity's exiting transition completes before starting.
+ * @param allow true to start the transition when possible or false to wait until the
+ * called Activity's exiting transition completes.
+ */
+ public void setAllowExitTransitionOverlap(boolean allow) {}
+
+ /**
+ * Returns how the transition set in
+ * {@link #setExitTransition(android.transition.Transition)} overlaps with the exit
+ * transition of the called Activity when reentering after if finishes. When true,
+ * the transition will start as soon as possible. When false, the transition will wait
+ * until the called Activity's exiting transition completes before starting.
+ * @return true when the transition should start when possible or false when it should wait
+ * until the called Activity's exiting transition completes.
+ */
+ public boolean getAllowExitTransitionOverlap() { return true; }
}
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 ae7cd26..4fde1e4 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
@@ -72,14 +76,7 @@ import java.io.PrintWriter;
public interface WindowManagerPolicy {
// Policy flags. These flags are also defined in frameworks/base/include/ui/Input.h.
public final static int FLAG_WAKE = 0x00000001;
- public final static int FLAG_WAKE_DROPPED = 0x00000002;
- public final static int FLAG_SHIFT = 0x00000004;
- public final static int FLAG_CAPS_LOCK = 0x00000008;
- public final static int FLAG_ALT = 0x00000010;
- public final static int FLAG_ALT_GR = 0x00000020;
- public final static int FLAG_MENU = 0x00000040;
- public final static int FLAG_LAUNCHER = 0x00000080;
- public final static int FLAG_VIRTUAL = 0x00000100;
+ public final static int FLAG_VIRTUAL = 0x00000002;
public final static int FLAG_INJECTED = 0x01000000;
public final static int FLAG_TRUSTED = 0x02000000;
@@ -447,6 +444,11 @@ public interface WindowManagerPolicy {
/** Screen turned off because of timeout */
public final int OFF_BECAUSE_OF_TIMEOUT = 3;
+ /** @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;
@@ -994,6 +996,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.
*
@@ -1002,7 +1012,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
@@ -1017,7 +1028,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.
@@ -1066,7 +1078,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.
@@ -1093,6 +1105,7 @@ public interface WindowManagerPolicy {
* @see WindowManagerPolicy#USER_ROTATION_LOCKED
* @see WindowManagerPolicy#USER_ROTATION_FREE
*/
+ @UserRotationMode
public int getUserRotationMode();
/**
@@ -1103,12 +1116,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);
@@ -1131,6 +1144,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.
*
@@ -1166,11 +1184,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..bc2d7ec 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;
}
@@ -256,7 +259,7 @@ public final class InputMethodInfo implements Parcelable {
public InputMethodInfo(String packageName, String className,
CharSequence label, String settingsActivity) {
this(buildDummyResolveInfo(packageName, className, label), false, settingsActivity, null,
- 0, false);
+ 0, false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */);
}
/**
@@ -266,17 +269,26 @@ public final class InputMethodInfo implements Parcelable {
public InputMethodInfo(ResolveInfo ri, boolean isAuxIme,
String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId,
boolean forceDefault) {
+ this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId,
+ forceDefault, true /* supportsSwitchingToNextInputMethod */);
+ }
+
+ /**
+ * Temporary API for creating a built-in input method for test.
+ * @hide
+ */
+ public InputMethodInfo(ResolveInfo ri, boolean isAuxIme,
+ String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId,
+ boolean forceDefault, boolean supportsSwitchingToNextInputMethod) {
final ServiceInfo si = ri.serviceInfo;
mService = ri;
mId = new ComponentName(si.packageName, si.name).flattenToShortString();
mSettingsActivityName = settingsActivity;
mIsDefaultResId = isDefaultResId;
mIsAuxIme = isAuxIme;
- if (subtypes != null) {
- mSubtypes.addAll(subtypes);
- }
+ mSubtypes = new InputMethodSubtypeArray(subtypes);
mForceDefault = forceDefault;
- mSupportsSwitchingToNextInputMethod = true;
+ mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod;
}
private static ResolveInfo buildDummyResolveInfo(String packageName, String className,
@@ -338,7 +350,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 +360,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 +374,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 +386,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 +431,7 @@ public final class InputMethodInfo implements Parcelable {
pw.println(prefix + "Service:");
mService.dump(pw, prefix + " ");
}
-
+
@Override
public String toString() {
return "InputMethodInfo{" + mId
@@ -430,7 +442,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 +479,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 +491,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..0227873 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;
@@ -316,6 +317,10 @@ public final class InputMethodManager {
int mCursorSelEnd;
int mCursorCandStart;
int mCursorCandEnd;
+ /**
+ * The buffer to retrieve the view location in screen coordinates in {@link #updateCursor}.
+ */
+ private final int[] mViewTopLeft = new int[2];
// -----------------------------------------------------------
@@ -334,6 +339,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 +356,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 +487,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 +557,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 +736,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 +1417,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 +1487,44 @@ 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) != 0;
+ }
}
-
+
+ /**
+ * Returns true if the current input method wants to receive the cursor rectangle in
+ * screen coordinates rather than local coordinates in the attached view.
+ *
+ * @hide
+ */
+ public boolean usesScreenCoordinatesForCursorLocked() {
+ // {@link InputMethodService#CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT} also means
+ // that {@link InputMethodService#onUpdateCursor} should provide the cursor rectangle
+ // in screen coordinates rather than local coordinates.
+ return (mCursorAnchorMonitorMode &
+ InputMethodService.CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT) != 0;
+ }
+
+ /**
+ * 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.
*/
@@ -1470,15 +1536,18 @@ public final class InputMethodManager {
|| mCurrentTextBoxAttribute == null || mCurMethod == null) {
return;
}
-
mTmpCursorRect.set(left, top, right, bottom);
if (!mCursorRect.equals(mTmpCursorRect)) {
if (DEBUG) Log.d(TAG, "updateCursor");
try {
if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod);
- mCurMethod.updateCursor(mTmpCursorRect);
mCursorRect.set(mTmpCursorRect);
+ if (usesScreenCoordinatesForCursorLocked()) {
+ view.getLocationOnScreen(mViewTopLeft);
+ mTmpCursorRect.offset(mViewTopLeft[0], mViewTopLeft[1]);
+ }
+ mCurMethod.updateCursor(mTmpCursorRect);
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
@@ -1805,6 +1874,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 +1923,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/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 98ef66e..7c32c5b 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -171,6 +171,38 @@ public abstract class WebSettings {
}
/**
+ * Used with {@link #setMixedContentMode}
+ *
+ * In this mode, the WebView will allow a secure origin to load content from any other origin,
+ * even if that origin is insecure. This is the least secure mode of operation for the WebView,
+ * and where possible apps should not set this mode.
+ */
+ public static final int MIXED_CONTENT_ALWAYS_ALLOW = 0;
+
+ /**
+ * Used with {@link #setMixedContentMode}
+ *
+ * In this mode, the WebView will not allow a secure origin to load content from an insecure
+ * origin. This is the preferred and most secure mode of operation for the WebView and apps are
+ * strongly advised to use this mode.
+ */
+ public static final int MIXED_CONTENT_NEVER_ALLOW = 1;
+
+ /**
+ * Used with {@link #setMixedContentMode}
+ *
+ * In this mode, the WebView will attempt to be compatible with the approach of a modern web
+ * browser with regard to mixed content. Some insecure content may be allowed to be loaded by
+ * a secure origin and other types of content will be blocked. The types of content are allowed
+ * or blocked may change release to release and are not explicitly defined.
+ *
+ * This mode is intended to be used by apps that are not in control of the content that they
+ * render but desire to operate in a reasonably secure environment. For highest security, apps
+ * are recommended to use {@link #MIXED_CONTENT_NEVER_ALLOW}.
+ */
+ public static final int MIXED_CONTENT_COMPATIBILITY_MODE = 2;
+
+ /**
* Hidden constructor to prevent clients from creating a new settings
* instance or deriving the class.
*
@@ -1403,4 +1435,29 @@ public abstract class WebSettings {
public int getCacheMode() {
throw new MustOverrideException();
}
+
+ /**
+ * Configures the WebView's behavior when a secure origin attempts to load a resource from an
+ * insecure origin.
+ *
+ * By default, apps that target {@link android.os.Build.VERSION_CODES#KITKAT} or below default
+ * to {@link #MIXED_CONTENT_ALWAYS_ALLOW}. Apps targeting
+ * {@link android.os.Build.VERSION_CODES#L} default to {@link #MIXED_CONTENT_NEVER_ALLOW}.
+ *
+ * The preferred and most secure mode of operation for the WebView is
+ * {@link #MIXED_CONTENT_NEVER_ALLOW} and use of {@link #MIXED_CONTENT_ALWAYS_ALLOW} is
+ * strongly discouraged.
+ *
+ * @param mode The mixed content mode to use. One of {@link #MIXED_CONTENT_NEVER_ALLOW},
+ * {@link #MIXED_CONTENT_NEVER_ALLOW} or {@link #MIXED_CONTENT_COMPATIBILITY_MODE}.
+ */
+ public abstract void setMixedContentMode(int mode);
+
+ /**
+ * Gets the current behavior of the WebView with regard to loading insecure content from a
+ * secure origin.
+ * @return The current setting, one of {@link #MIXED_CONTENT_NEVER_ALLOW},
+ * {@link #MIXED_CONTENT_NEVER_ALLOW} or {@link #MIXED_CONTENT_COMPATIBILITY_MODE}.
+ */
+ public abstract int getMixedContentMode();
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index d53bb74..efb246a 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,19 +462,38 @@ 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
*
- * @deprecated Private browsing is no longer supported directly via
+ * @deprecated Private browsing is no longer supported directly via
* WebView and will be removed in a future release. Prefer using
* {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager}
* 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");
}
@@ -670,7 +701,7 @@ public class WebView extends AbsoluteLayout
*/
@Deprecated
public static void enablePlatformNotifications() {
- getFactory().getStatics().setPlatformNotificationsEnabled(true);
+ // noop
}
/**
@@ -682,7 +713,7 @@ public class WebView extends AbsoluteLayout
*/
@Deprecated
public static void disablePlatformNotifications() {
- getFactory().getStatics().setPlatformNotificationsEnabled(false);
+ // noop
}
/**
@@ -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);
+ }
}
/**
@@ -1093,9 +1136,18 @@ public class WebView extends AbsoluteLayout
}
/**
+ * @deprecated Use {@link #createPrintDocumentAdapter(String)} which requires user
+ * to provide a print document name.
+ */
+ @Deprecated
+ public PrintDocumentAdapter createPrintDocumentAdapter() {
+ checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "createPrintDocumentAdapter");
+ return mProvider.createPrintDocumentAdapter("default");
+ }
+
+ /**
* Creates a PrintDocumentAdapter that provides the content of this Webview for printing.
- * Only supported for API levels
- * {@link android.os.Build.VERSION_CODES#KITKAT} and above.
*
* The adapter works by converting the Webview contents to a PDF stream. The Webview cannot
* be drawn during the conversion process - any such draws are undefined. It is recommended
@@ -1103,11 +1155,14 @@ public class WebView extends AbsoluteLayout
* temporarily hide a visible WebView by using a custom PrintDocumentAdapter instance
* wrapped around the object returned and observing the onStart and onFinish methods. See
* {@link android.print.PrintDocumentAdapter} for more information.
+ *
+ * @param documentName The user-facing name of the printed document. See
+ * {@link android.print.PrintDocumentInfo}
*/
- public PrintDocumentAdapter createPrintDocumentAdapter() {
+ public PrintDocumentAdapter createPrintDocumentAdapter(String documentName) {
checkThread();
if (DebugFlags.TRACE_API) Log.d(LOGTAG, "createPrintDocumentAdapter");
- return mProvider.createPrintDocumentAdapter();
+ return mProvider.createPrintDocumentAdapter(documentName);
}
/**
@@ -1421,6 +1476,24 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Clears the client certificate preferences table stored in response
+ * to proceeding/cancelling client cert requests. Note that webview
+ * automatically clears these preferences when it receives a
+ * {@link KeyChain.ACTION_STORAGE_CHANGED}
+ *
+ * @param resultCallback A callback to be invoked when client certs are cleared.
+ * The embedder can pass null if not interested in the callback.
+ *
+ * TODO(sgurun) unhide
+ * @hide
+ */
+ public void clearClientCertPreferences(ValueCallback<Void> resultCallback) {
+ checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearClientCertPreferences");
+ mProvider.clearClientCertPreferences(resultCallback);
+ }
+
+ /**
* Gets the WebBackForwardList for this WebView. This contains the
* back/forward list for use in querying each item in the history stack.
* This is a copy of the private WebBackForwardList so it contains only a
@@ -1537,6 +1610,8 @@ public class WebView extends AbsoluteLayout
* @return the address, or if no address is found, null
*/
public static String findAddress(String addr) {
+ // TODO: Rewrite this in Java so it is not needed to start up chromium
+ // Could also be deprecated
return getFactory().getStatics().findAddress(addr);
}
@@ -1598,6 +1673,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 +1739,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 +2190,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/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index fb842ff..107ae4f 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -19,9 +19,12 @@ package android.webkit;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Message;
+import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.ViewRootImpl;
+import java.security.Principal;
+
public class WebViewClient {
/**
@@ -204,7 +207,7 @@ public class WebViewClient {
handler.cancel();
}
- /**
+ /**
* Notify the host application to handle a SSL client certificate
* request. The host application is responsible for showing the UI
* if desired and providing the keys. There are three ways to
@@ -270,11 +273,43 @@ public class WebViewClient {
*
* @param view The WebView that is initiating the callback.
* @param event The key event.
+ * @deprecated This method is subsumed by the more generic onUnhandledInputEvent.
*/
+ @Deprecated
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
+ onUnhandledInputEventInternal(view, event);
+ }
+
+ /**
+ * Notify the host application that a input event was not handled by the WebView.
+ * Except system keys, WebView always consumes input events in the normal flow
+ * or if shouldOverrideKeyEvent returns true. This is called asynchronously
+ * from where the event is dispatched. It gives the host application a chance
+ * to handle the unhandled input events.
+ *
+ * Note that if the event is a {@link android.view.MotionEvent}, then it's lifetime is only
+ * that of the function call. If the WebViewClient wishes to use the event beyond that, then it
+ * <i>must</i> create a copy of the event.
+ *
+ * It is the responsibility of overriders of this method to call
+ * {@link #onUnhandledKeyEvent(WebView, KeyEvent)}
+ * when appropriate if they wish to continue receiving events through it.
+ *
+ * @param view The WebView that is initiating the callback.
+ * @param event The input event.
+ */
+ public void onUnhandledInputEvent(WebView view, InputEvent event) {
+ if (event instanceof KeyEvent) {
+ onUnhandledKeyEvent(view, (KeyEvent) event);
+ return;
+ }
+ onUnhandledInputEventInternal(view, event);
+ }
+
+ private void onUnhandledInputEventInternal(WebView view, InputEvent event) {
ViewRootImpl root = view.getViewRootImpl();
if (root != null) {
- root.dispatchUnhandledKey(event);
+ root.dispatchUnhandledInputEvent(event);
}
}
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..efa5497 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;
@@ -147,7 +148,7 @@ public interface WebViewProvider {
public Picture capturePicture();
- public PrintDocumentAdapter createPrintDocumentAdapter();
+ public PrintDocumentAdapter createPrintDocumentAdapter(String documentName);
public float getScale();
@@ -197,6 +198,8 @@ public interface WebViewProvider {
public void clearSslPreferences();
+ public void clearClientCertPreferences(ValueCallback<Void> resultCallback);
+
public WebBackForwardList copyBackForwardList();
public void setFindListener(WebView.FindListener listener);
@@ -245,6 +248,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..0966be3 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;
@@ -106,6 +110,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* @see #setTranscriptMode(int)
*/
public static final int TRANSCRIPT_MODE_DISABLED = 0;
+
/**
* The list will automatically scroll to the bottom when a data set change
* notification is received and only if the last item is already visible
@@ -114,6 +119,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* @see #setTranscriptMode(int)
*/
public static final int TRANSCRIPT_MODE_NORMAL = 1;
+
/**
* The list will automatically scroll to the bottom, no matter what items
* are currently visible.
@@ -417,7 +423,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
@@ -539,7 +545,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
/**
* The last CheckForTap runnable we posted, if any
*/
- private Runnable mPendingCheckForTap;
+ private CheckForTap mPendingCheckForTap;
/**
* The last CheckForKeyLongPress runnable we posted, if any
@@ -581,7 +587,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 +705,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 +790,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 +830,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 +1262,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 +1326,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 +1345,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 +1368,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 +1448,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 +1498,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 +1629,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));
}
@@ -2075,7 +2128,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
+
mInLayout = true;
+
final int childCount = getChildCount();
if (changed) {
for (int i = 0; i < childCount; i++) {
@@ -2090,8 +2145,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 +2176,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 +2316,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 +2335,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 +2347,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 +2363,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) {
@@ -2416,8 +2491,30 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+ /**
+ * Positions the selector in a way that mimics keyboard focus. If the
+ * selector drawable supports hotspots, this manages the focus hotspot.
+ */
+ void positionSelectorLikeFocus(int position, View sel) {
+ positionSelector(position, sel);
+
+ final Drawable selector = mSelector;
+ if (selector != null && selector.supportsHotspots() && position != INVALID_POSITION) {
+ final Rect bounds = mSelectorRect;
+ final float x = bounds.exactCenterX();
+ final float y = bounds.exactCenterY();
+ selector.setHotspot(R.attr.state_focused, x, y);
+ }
+ }
+
void positionSelector(int position, View sel) {
if (position != INVALID_POSITION) {
+ if (mSelectorPosition != position) {
+ final Drawable selector = mSelector;
+ if (selector != null && selector.supportsHotspots()) {
+ selector.clearHotspots();
+ }
+ }
mSelectorPosition = position;
}
@@ -2506,8 +2603,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 +2663,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 +2825,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 +2875,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
removeCallbacks(mTouchModeReset);
mTouchModeReset.run();
}
+
+ mIsDetaching = false;
}
@Override
@@ -2836,8 +2937,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 +3140,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;
}
@@ -3110,7 +3211,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return INVALID_ROW_ID;
}
- final class CheckForTap implements Runnable {
+ private final class CheckForTap implements Runnable {
+ float x;
+ float y;
+
@Override
public void run() {
if (mTouchMode == TOUCH_MODE_DOWN) {
@@ -3130,7 +3234,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final boolean longClickable = isLongClickable();
if (mSelector != null) {
- Drawable d = mSelector.getCurrent();
+ final Drawable d = mSelector.getCurrent();
if (d != null && d instanceof TransitionDrawable) {
if (longClickable) {
((TransitionDrawable) d).startTransition(longPressTimeout);
@@ -3138,6 +3242,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
((TransitionDrawable) d).resetTransition();
}
}
+ if (d.supportsHotspots()) {
+ d.setHotspot(R.attr.state_pressed, x, y);
+ }
}
if (longClickable) {
@@ -3402,7 +3509,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 +3517,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;
}
@@ -3521,6 +3628,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mPendingCheckForTap = new CheckForTap();
}
+ mPendingCheckForTap.x = ev.getX();
+ mPendingCheckForTap.y = ev.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
}
}
@@ -3630,6 +3739,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (d != null && d instanceof TransitionDrawable) {
((TransitionDrawable) d).resetTransition();
}
+ if (mSelector.supportsHotspots()) {
+ mSelector.setHotspot(R.attr.state_pressed, x, ev.getY());
+ }
}
if (mTouchModeReset != null) {
removeCallbacks(mTouchModeReset);
@@ -3641,7 +3753,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mTouchMode = TOUCH_MODE_REST;
child.setPressed(false);
setPressed(false);
- if (!mDataChanged && isAttachedToWindow()) {
+ if (mSelector != null && mSelector.supportsHotspots()) {
+ mSelector.removeHotspot(R.attr.state_pressed);
+ }
+ if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
performClick.run();
}
}
@@ -3900,7 +4015,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 +4031,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 +4039,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 +4436,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 +4458,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 +4490,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 +4508,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 +4518,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 +5967,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 +6237,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 +6343,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 +6415,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 +6488,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 +6591,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;
+ }
+ }
+
+ 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 {
+ // 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);
}
- return scrapViews.remove(size - 1);
} else {
- return null;
+ 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..225cd6d 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -29,6 +29,8 @@ import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import com.android.internal.R;
+
public abstract class AbsSeekBar extends ProgressBar {
private Drawable mThumb;
private int mThumbOffset;
@@ -65,11 +67,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
@@ -285,28 +291,39 @@ public abstract class AbsSeekBar extends ProgressBar {
*/
private void setThumbPos(int w, Drawable thumb, float scale, int gap) {
int available = w - mPaddingLeft - mPaddingRight;
- int thumbWidth = thumb.getIntrinsicWidth();
- int thumbHeight = thumb.getIntrinsicHeight();
+ final int thumbWidth = thumb.getIntrinsicWidth();
+ final int thumbHeight = thumb.getIntrinsicHeight();
available -= thumbWidth;
// The extra space for the thumb to move on the track
available += mThumbOffset * 2;
- int thumbPos = (int) (scale * available + 0.5f);
+ final int thumbPos = (int) (scale * available + 0.5f);
- int topBound, bottomBound;
+ final int top, bottom;
if (gap == Integer.MIN_VALUE) {
- Rect oldBounds = thumb.getBounds();
- topBound = oldBounds.top;
- bottomBound = oldBounds.bottom;
+ final Rect oldBounds = thumb.getBounds();
+ top = oldBounds.top;
+ bottom = oldBounds.bottom;
} else {
- topBound = gap;
- bottomBound = gap + thumbHeight;
+ top = gap;
+ bottom = gap + thumbHeight;
}
-
- // Canvas will be translated, so 0,0 is where we start drawing
+
final int left = (isLayoutRtl() && mMirrorForRtl) ? available - thumbPos : thumbPos;
- thumb.setBounds(left, topBound, left + thumbWidth, bottomBound);
+ final int right = left + thumbWidth;
+
+ final Drawable background = getBackground();
+ if (background != null && background.supportsHotspots()) {
+ final Rect bounds = mThumb.getBounds();
+ final int offsetX = mPaddingLeft - mThumbOffset;
+ final int offsetY = mPaddingTop;
+ background.setHotspotBounds(left + offsetX, bounds.top + offsetY,
+ right + offsetX, bounds.bottom + offsetY);
+ }
+
+ // Canvas will be translated, so 0,0 is where we start drawing
+ thumb.setBounds(left, top, right, bottom);
}
/**
@@ -324,6 +341,7 @@ public abstract class AbsSeekBar extends ProgressBar {
@Override
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
+
if (mThumb != null) {
canvas.save();
// Translate the padding. For the x, we need to allow the thumb to
@@ -420,10 +438,24 @@ public abstract class AbsSeekBar extends ProgressBar {
return true;
}
+ private void setHotspot(int id, float x, float y) {
+ final Drawable bg = getBackground();
+ if (bg != null && bg.supportsHotspots()) {
+ bg.setHotspot(id, x, y);
+ }
+ }
+
+ private void clearHotspot(int id) {
+ final Drawable bg = getBackground();
+ if (bg != null && bg.supportsHotspots()) {
+ bg.removeHotspot(id);
+ }
+ }
+
private void trackTouchEvent(MotionEvent event) {
final int width = getWidth();
final int available = width - mPaddingLeft - mPaddingRight;
- int x = (int)event.getX();
+ final int x = (int) event.getX();
float scale;
float progress = 0;
if (isLayoutRtl() && mMirrorForRtl) {
@@ -447,7 +479,8 @@ public abstract class AbsSeekBar extends ProgressBar {
}
final int max = getMax();
progress += scale * max;
-
+
+ setHotspot(R.attr.state_pressed, x, (int) event.getY());
setProgress((int) progress, true);
}
@@ -473,6 +506,7 @@ public abstract class AbsSeekBar extends ProgressBar {
* canceled.
*/
void onStopTrackingTouch() {
+ clearHotspot(R.attr.state_pressed);
mIsDragging = false;
}
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index f26527f..6a4ad75 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -64,12 +64,16 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
this(context, attrs, 0);
}
- public AbsSpinner(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public AbsSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AbsSpinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
initAbsSpinner();
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.AbsSpinner, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.AbsSpinner, defStyleAttr, defStyleRes);
CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries);
if (entries != null) {
diff --git a/core/java/android/widget/AbsoluteLayout.java b/core/java/android/widget/AbsoluteLayout.java
index 7df6aab..4ce0d5d 100644
--- a/core/java/android/widget/AbsoluteLayout.java
+++ b/core/java/android/widget/AbsoluteLayout.java
@@ -40,16 +40,19 @@ import android.widget.RemoteViews.RemoteView;
@RemoteView
public class AbsoluteLayout extends ViewGroup {
public AbsoluteLayout(Context context) {
- super(context);
+ this(context, null);
}
public AbsoluteLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
}
- public AbsoluteLayout(Context context, AttributeSet attrs,
- int defStyle) {
- super(context, attrs, defStyle);
+ public AbsoluteLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AbsoluteLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index fe1cf72..e4575e5 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package com.android.internal.view.menu;
+package android.widget;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
-import android.transition.Transition;
-import android.transition.TransitionManager;
import android.util.SparseBooleanArray;
import android.view.ActionProvider;
import android.view.Gravity;
@@ -32,17 +30,23 @@ import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.ImageButton;
-import android.widget.ListPopupWindow;
import android.widget.ListPopupWindow.ForwardingListener;
import com.android.internal.transition.ActionBarTransition;
import com.android.internal.view.ActionBarPolicy;
-import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView;
+import com.android.internal.view.menu.ActionMenuItemView;
+import com.android.internal.view.menu.BaseMenuPresenter;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuItemImpl;
+import com.android.internal.view.menu.MenuPopupHelper;
+import com.android.internal.view.menu.MenuView;
+import com.android.internal.view.menu.SubMenuBuilder;
import java.util.ArrayList;
/**
* MenuPresenter for building action menus as seen in the action bar and action modes.
+ *
+ * @hide
*/
public class ActionMenuPresenter extends BaseMenuPresenter
implements ActionProvider.SubUiVisibilityListener {
@@ -70,6 +74,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
private ActionButtonSubmenu mActionButtonPopup;
private OpenOverflowRunnable mPostedOpenRunnable;
+ private ActionMenuPopupCallback mPopupCallback;
final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
int mOpenSubMenuId;
@@ -173,33 +178,17 @@ public class ActionMenuPresenter extends BaseMenuPresenter
}
@Override
- public void bindItemView(final MenuItemImpl item, MenuView.ItemView itemView) {
+ public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
itemView.initialize(item, 0);
final ActionMenuView menuView = (ActionMenuView) mMenuView;
final ActionMenuItemView actionItemView = (ActionMenuItemView) itemView;
actionItemView.setItemInvoker(menuView);
- if (item.hasSubMenu()) {
- actionItemView.setOnTouchListener(new ForwardingListener(actionItemView) {
- @Override
- public ListPopupWindow getPopup() {
- return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
- }
-
- @Override
- protected boolean onForwardingStarted() {
- return onSubMenuSelected((SubMenuBuilder) item.getSubMenu());
- }
-
- @Override
- protected boolean onForwardingStopped() {
- return dismissPopupMenus();
- }
- });
- } else {
- actionItemView.setOnTouchListener(null);
+ if (mPopupCallback == null) {
+ mPopupCallback = new ActionMenuPopupCallback();
}
+ actionItemView.setPopupCallback(mPopupCallback);
}
@Override
@@ -553,6 +542,10 @@ public class ActionMenuPresenter extends BaseMenuPresenter
}
}
+ public void setMenuView(ActionMenuView menuView) {
+ mMenuView = menuView;
+ }
+
private static class SavedState implements Parcelable {
public int openSubMenuId;
@@ -585,7 +578,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
};
}
- private class OverflowMenuButton extends ImageButton implements ActionMenuChildView {
+ private class OverflowMenuButton extends ImageButton implements ActionMenuView.ActionMenuChildView {
public OverflowMenuButton(Context context) {
super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
@@ -647,16 +640,6 @@ public class ActionMenuPresenter extends BaseMenuPresenter
}
@Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
- // Fill available height
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(
- MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setCanOpenPopup(true);
@@ -714,14 +697,14 @@ public class ActionMenuPresenter extends BaseMenuPresenter
}
}
- private class PopupPresenterCallback implements MenuPresenter.Callback {
+ private class PopupPresenterCallback implements Callback {
@Override
public boolean onOpenSubMenu(MenuBuilder subMenu) {
if (subMenu == null) return false;
mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId();
- final MenuPresenter.Callback cb = getCallback();
+ final Callback cb = getCallback();
return cb != null ? cb.onOpenSubMenu(subMenu) : false;
}
@@ -730,7 +713,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
if (menu instanceof SubMenuBuilder) {
((SubMenuBuilder) menu).getRootMenu().close(false);
}
- final MenuPresenter.Callback cb = getCallback();
+ final Callback cb = getCallback();
if (cb != null) {
cb.onCloseMenu(menu, allMenusAreClosing);
}
@@ -753,4 +736,11 @@ public class ActionMenuPresenter extends BaseMenuPresenter
mPostedOpenRunnable = null;
}
}
+
+ private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback {
+ @Override
+ public ListPopupWindow getPopup() {
+ return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
+ }
+ }
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index 16a2031..3975edf 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -13,22 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.internal.view.menu;
+package android.widget;
import android.content.Context;
import android.content.res.Configuration;
-import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
-import android.widget.LinearLayout;
-import com.android.internal.R;
+import com.android.internal.view.menu.ActionMenuItemView;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuItemImpl;
+import com.android.internal.view.menu.MenuPresenter;
+import com.android.internal.view.menu.MenuView;
/**
- * @hide
+ * ActionMenuView is a presentation of a series of menu options as a View. It provides
+ * several top level options as action buttons while spilling remaining options over as
+ * items in an overflow menu. This allows applications to present packs of actions inline with
+ * specific or repeating content.
*/
public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
private static final String TAG = "ActionMenuView";
@@ -44,8 +51,8 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
private int mFormatItemsWidth;
private int mMinCellSize;
private int mGeneratedItemPadding;
- private int mMeasuredExtraWidth;
- private int mMaxItemHeight;
+
+ private OnMenuItemClickListener mOnMenuItemClickListener;
public ActionMenuView(Context context) {
this(context, null);
@@ -57,26 +64,13 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
final float density = context.getResources().getDisplayMetrics().density;
mMinCellSize = (int) (MIN_CELL_SIZE * density);
mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar,
- R.attr.actionBarStyle, 0);
- mMaxItemHeight = a.getDimensionPixelSize(R.styleable.ActionBar_height, 0);
- a.recycle();
}
+ /** @hide */
public void setPresenter(ActionMenuPresenter presenter) {
mPresenter = presenter;
}
- public boolean isExpandedFormat() {
- return mFormatItems;
- }
-
- public void setMaxItemHeight(int maxItemHeight) {
- mMaxItemHeight = maxItemHeight;
- requestLayout();
- }
-
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -88,6 +82,10 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
}
}
+ 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.
@@ -106,11 +104,11 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
mMenu.onItemsChanged(true);
}
- if (mFormatItems) {
+ final int childCount = getChildCount();
+ if (mFormatItems && childCount > 0) {
onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
} else {
// Previous measurement at exact format may have set margins - reset them.
- final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
@@ -129,10 +127,8 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
final int widthPadding = getPaddingLeft() + getPaddingRight();
final int heightPadding = getPaddingTop() + getPaddingBottom();
- final int itemHeightSpec = heightMode == MeasureSpec.EXACTLY
- ? MeasureSpec.makeMeasureSpec(heightSize - heightPadding, MeasureSpec.EXACTLY)
- : MeasureSpec.makeMeasureSpec(
- Math.min(mMaxItemHeight, heightSize - heightPadding), MeasureSpec.AT_MOST);
+ final int itemHeightSpec = getChildMeasureSpec(heightMeasureSpec, heightPadding,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
widthSize -= widthPadding;
@@ -333,7 +329,6 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
}
setMeasuredDimension(widthSize, heightSize);
- mMeasuredExtraWidth = cellsRemaining * cellSize;
}
/**
@@ -496,10 +491,12 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
mPresenter.dismissPopupMenus();
}
+ /** @hide */
public boolean isOverflowReserved() {
return mReserveOverflow;
}
-
+
+ /** @hide */
public void setOverflowReserved(boolean reserveOverflow) {
mReserveOverflow = reserveOverflow;
}
@@ -536,24 +533,53 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
return p != null && p instanceof LayoutParams;
}
+ /** @hide */
public LayoutParams generateOverflowButtonLayoutParams() {
LayoutParams result = generateDefaultLayoutParams();
result.isOverflowButton = true;
return result;
}
+ /** @hide */
public boolean invokeItem(MenuItemImpl item) {
return mMenu.performItemAction(item, 0);
}
+ /** @hide */
public int getWindowAnimations() {
return 0;
}
+ /** @hide */
public void initialize(MenuBuilder menu) {
mMenu = menu;
}
+ /**
+ * Returns the Menu object that this ActionMenuView is currently presenting.
+ *
+ * <p>Applications should use this method to obtain the ActionMenuView's Menu object
+ * and inflate or add content to it as necessary.</p>
+ *
+ * @return the Menu presented by this view
+ */
+ public Menu getMenu() {
+ if (mMenu == null) {
+ final Context context = getContext();
+ mMenu = new MenuBuilder(context);
+ 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) {
@@ -575,23 +601,72 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
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) {
@@ -612,6 +687,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
isOverflowButton = false;
}
+ /** @hide */
public LayoutParams(int width, int height, boolean isOverflowButton) {
super(width, height);
this.isOverflowButton = isOverflowButton;
diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java
index 8612964..f9af2f9 100644
--- a/core/java/android/widget/ActivityChooserView.java
+++ b/core/java/android/widget/ActivityChooserView.java
@@ -18,7 +18,6 @@ package android.widget;
import com.android.internal.R;
-import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -31,7 +30,6 @@ import android.util.AttributeSet;
import android.util.Log;
import android.view.ActionProvider;
import android.view.LayoutInflater;
-import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -204,13 +202,32 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
*
* @param context The application environment.
* @param attrs A collection of attributes.
- * @param defStyle The default style to apply to this view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
*/
- public ActivityChooserView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param context The application environment.
+ * @param attrs A collection of attributes.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ */
+ public ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
TypedArray attributesArray = context.obtainStyledAttributes(attrs,
- R.styleable.ActivityChooserView, defStyle, 0);
+ R.styleable.ActivityChooserView, defStyleAttr, defStyleRes);
mInitialActivityCount = attributesArray.getInt(
R.styleable.ActivityChooserView_initialActivityCount,
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index a06344f..1da22ca 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -223,15 +223,19 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
boolean mBlockLayoutRequests = false;
public AdapterView(Context context) {
- super(context);
+ this(context, null);
}
public AdapterView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
}
- public AdapterView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public AdapterView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AdapterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
// If not explicitly specified this view is important for accessibility.
if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 90e949a..1bc2f4b 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -173,10 +173,15 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
}
public AdapterViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AdapterViewAnimator(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.AdapterViewAnimator, defStyleAttr, 0);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.AdapterViewAnimator, defStyleAttr, defStyleRes);
int resource = a.getResourceId(
com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0);
if (resource > 0) {
diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java
index aea029b..3b026bd 100644
--- a/core/java/android/widget/AdapterViewFlipper.java
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -59,10 +59,19 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
}
public AdapterViewFlipper(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
+ }
+
+ public AdapterViewFlipper(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AdapterViewFlipper(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.AdapterViewFlipper);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.AdapterViewFlipper, defStyleAttr, defStyleRes);
mFlipInterval = a.getInt(
com.android.internal.R.styleable.AdapterViewFlipper_flipInterval, DEFAULT_INTERVAL);
mAutoStart = a.getBoolean(
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index c7da818..5b80648 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -67,27 +67,30 @@ public class AnalogClock extends View {
this(context, attrs, 0);
}
- public AnalogClock(Context context, AttributeSet attrs,
- int defStyle) {
- super(context, attrs, defStyle);
- Resources r = mContext.getResources();
- TypedArray a =
- context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.AnalogClock, defStyle, 0);
+ public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final Resources r = context.getResources();
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.AnalogClock, defStyleAttr, defStyleRes);
mDial = a.getDrawable(com.android.internal.R.styleable.AnalogClock_dial);
if (mDial == null) {
- mDial = r.getDrawable(com.android.internal.R.drawable.clock_dial);
+ mDial = context.getDrawable(com.android.internal.R.drawable.clock_dial);
}
mHourHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_hour);
if (mHourHand == null) {
- mHourHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_hour);
+ mHourHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_hour);
}
mMinuteHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_minute);
if (mMinuteHand == null) {
- mMinuteHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
+ mMinuteHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
}
mCalendar = new Time();
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index 34cfea5..10e56c7 100644
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -322,7 +322,7 @@ public class AppSecurityPermissions {
CharSequence grpName, CharSequence description, boolean dangerous) {
LayoutInflater inflater = (LayoutInflater)context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
- Drawable icon = context.getResources().getDrawable(dangerous
+ Drawable icon = context.getDrawable(dangerous
? R.drawable.ic_bullet_key_permission : R.drawable.ic_text_dot);
return getPermissionItemViewOld(context, inflater, grpName,
description, dangerous, icon);
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index f0eb94f..eb232fd 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -133,17 +133,21 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
this(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle);
}
- public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AutoCompleteTextView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
mPopup = new ListPopupWindow(context, attrs,
com.android.internal.R.attr.autoCompleteTextViewStyle);
mPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW);
- TypedArray a =
- context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.AutoCompleteTextView, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.AutoCompleteTextView, defStyleAttr, defStyleRes);
mThreshold = a.getInt(
R.styleable.AutoCompleteTextView_completionThreshold, 2);
@@ -362,7 +366,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
* @attr ref android.R.styleable#PopupWindow_popupBackground
*/
public void setDropDownBackgroundResource(int id) {
- mPopup.setBackgroundDrawable(getResources().getDrawable(id));
+ mPopup.setBackgroundDrawable(getContext().getDrawable(id));
}
/**
diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java
index 2ac56ac..1663620 100644
--- a/core/java/android/widget/Button.java
+++ b/core/java/android/widget/Button.java
@@ -103,8 +103,12 @@ public class Button extends TextView {
this(context, attrs, com.android.internal.R.attr.buttonStyle);
}
- public Button(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public Button(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public Button(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java
index e90b460..ea60abb 100644
--- a/core/java/android/widget/CalendarView.java
+++ b/core/java/android/widget/CalendarView.java
@@ -80,234 +80,7 @@ public class CalendarView extends FrameLayout {
*/
private static final String LOG_TAG = CalendarView.class.getSimpleName();
- /**
- * Default value whether to show week number.
- */
- private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true;
-
- /**
- * The number of milliseconds in a day.e
- */
- private static final long MILLIS_IN_DAY = 86400000L;
-
- /**
- * The number of day in a week.
- */
- private static final int DAYS_PER_WEEK = 7;
-
- /**
- * The number of milliseconds in a week.
- */
- private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY;
-
- /**
- * Affects when the month selection will change while scrolling upe
- */
- private static final int SCROLL_HYST_WEEKS = 2;
-
- /**
- * How long the GoTo fling animation should last.
- */
- private static final int GOTO_SCROLL_DURATION = 1000;
-
- /**
- * The duration of the adjustment upon a user scroll in milliseconds.
- */
- private static final int ADJUSTMENT_SCROLL_DURATION = 500;
-
- /**
- * How long to wait after receiving an onScrollStateChanged notification
- * before acting on it.
- */
- private static final int SCROLL_CHANGE_DELAY = 40;
-
- /**
- * String for parsing dates.
- */
- private static final String DATE_FORMAT = "MM/dd/yyyy";
-
- /**
- * The default minimal date.
- */
- private static final String DEFAULT_MIN_DATE = "01/01/1900";
-
- /**
- * The default maximal date.
- */
- private static final String DEFAULT_MAX_DATE = "01/01/2100";
-
- private static final int DEFAULT_SHOWN_WEEK_COUNT = 6;
-
- private static final int DEFAULT_DATE_TEXT_SIZE = 14;
-
- private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6;
-
- private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12;
-
- private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2;
-
- private static final int UNSCALED_BOTTOM_BUFFER = 20;
-
- private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1;
-
- private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1;
-
- private final int mWeekSeperatorLineWidth;
-
- private int mDateTextSize;
-
- private Drawable mSelectedDateVerticalBar;
-
- private final int mSelectedDateVerticalBarWidth;
-
- private int mSelectedWeekBackgroundColor;
-
- private int mFocusedMonthDateColor;
-
- private int mUnfocusedMonthDateColor;
-
- private int mWeekSeparatorLineColor;
-
- private int mWeekNumberColor;
-
- private int mWeekDayTextAppearanceResId;
-
- private int mDateTextAppearanceResId;
-
- /**
- * The top offset of the weeks list.
- */
- private int mListScrollTopOffset = 2;
-
- /**
- * The visible height of a week view.
- */
- private int mWeekMinVisibleHeight = 12;
-
- /**
- * The visible height of a week view.
- */
- private int mBottomBuffer = 20;
-
- /**
- * The number of shown weeks.
- */
- private int mShownWeekCount;
-
- /**
- * Flag whether to show the week number.
- */
- private boolean mShowWeekNumber;
-
- /**
- * The number of day per week to be shown.
- */
- private int mDaysPerWeek = 7;
-
- /**
- * The friction of the week list while flinging.
- */
- private float mFriction = .05f;
-
- /**
- * Scale for adjusting velocity of the week list while flinging.
- */
- private float mVelocityScale = 0.333f;
-
- /**
- * The adapter for the weeks list.
- */
- private WeeksAdapter mAdapter;
-
- /**
- * The weeks list.
- */
- private ListView mListView;
-
- /**
- * The name of the month to display.
- */
- private TextView mMonthName;
-
- /**
- * The header with week day names.
- */
- private ViewGroup mDayNamesHeader;
-
- /**
- * Cached labels for the week names header.
- */
- private String[] mDayLabels;
-
- /**
- * The first day of the week.
- */
- private int mFirstDayOfWeek;
-
- /**
- * Which month should be displayed/highlighted [0-11].
- */
- private int mCurrentMonthDisplayed = -1;
-
- /**
- * Used for tracking during a scroll.
- */
- private long mPreviousScrollPosition;
-
- /**
- * Used for tracking which direction the view is scrolling.
- */
- private boolean mIsScrollingUp = false;
-
- /**
- * The previous scroll state of the weeks ListView.
- */
- private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
-
- /**
- * The current scroll state of the weeks ListView.
- */
- private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
-
- /**
- * Listener for changes in the selected day.
- */
- private OnDateChangeListener mOnDateChangeListener;
-
- /**
- * Command for adjusting the position after a scroll/fling.
- */
- private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
-
- /**
- * Temporary instance to avoid multiple instantiations.
- */
- private Calendar mTempDate;
-
- /**
- * The first day of the focused month.
- */
- private Calendar mFirstDayOfMonth;
-
- /**
- * The start date of the range supported by this picker.
- */
- private Calendar mMinDate;
-
- /**
- * The end date of the range supported by this picker.
- */
- private Calendar mMaxDate;
-
- /**
- * Date format for parsing dates.
- */
- private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
-
- /**
- * The current locale.
- */
- private Locale mCurrentLocale;
+ private CalendarViewDelegate mDelegate;
/**
* The callback used to indicate the user changes the date.
@@ -330,91 +103,17 @@ public class CalendarView extends FrameLayout {
}
public CalendarView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
+ this(context, attrs, R.attr.calendarViewStyle);
}
- public CalendarView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, 0);
-
- // initialization based on locale
- setCurrentLocale(Locale.getDefault());
-
- TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.CalendarView,
- R.attr.calendarViewStyle, 0);
- mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber,
- DEFAULT_SHOW_WEEK_NUMBER);
- mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek,
- LocaleData.get(Locale.getDefault()).firstDayOfWeek);
- String minDate = attributesArray.getString(R.styleable.CalendarView_minDate);
- if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) {
- parseDate(DEFAULT_MIN_DATE, mMinDate);
- }
- String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate);
- if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) {
- parseDate(DEFAULT_MAX_DATE, mMaxDate);
- }
- if (mMaxDate.before(mMinDate)) {
- throw new IllegalArgumentException("Max date cannot be before min date.");
- }
- mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount,
- DEFAULT_SHOWN_WEEK_COUNT);
- mSelectedWeekBackgroundColor = attributesArray.getColor(
- R.styleable.CalendarView_selectedWeekBackgroundColor, 0);
- mFocusedMonthDateColor = attributesArray.getColor(
- R.styleable.CalendarView_focusedMonthDateColor, 0);
- mUnfocusedMonthDateColor = attributesArray.getColor(
- R.styleable.CalendarView_unfocusedMonthDateColor, 0);
- mWeekSeparatorLineColor = attributesArray.getColor(
- R.styleable.CalendarView_weekSeparatorLineColor, 0);
- mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0);
- mSelectedDateVerticalBar = attributesArray.getDrawable(
- R.styleable.CalendarView_selectedDateVerticalBar);
-
- mDateTextAppearanceResId = attributesArray.getResourceId(
- R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small);
- updateDateTextSize();
-
- mWeekDayTextAppearanceResId = attributesArray.getResourceId(
- R.styleable.CalendarView_weekDayTextAppearance,
- DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID);
- attributesArray.recycle();
-
- DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
- mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics);
- mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics);
- mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_BOTTOM_BUFFER, displayMetrics);
- mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics);
- mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics);
-
- LayoutInflater layoutInflater = (LayoutInflater) context
- .getSystemService(Service.LAYOUT_INFLATER_SERVICE);
- View content = layoutInflater.inflate(R.layout.calendar_view, null, false);
- addView(content);
-
- mListView = (ListView) findViewById(R.id.list);
- mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names);
- mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name);
-
- setUpHeader();
- setUpListView();
- setUpAdapter();
-
- // go to today or whichever is close to today min or max date
- mTempDate.setTimeInMillis(System.currentTimeMillis());
- if (mTempDate.before(mMinDate)) {
- goTo(mMinDate, false, true, true);
- } else if (mMaxDate.before(mTempDate)) {
- goTo(mMaxDate, false, true, true);
- } else {
- goTo(mTempDate, false, true, true);
- }
+ public CalendarView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
- invalidate();
+ public CalendarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ mDelegate = new LegacyCalendarViewDelegate(this, context, attrs, defStyleAttr, defStyleRes);
}
/**
@@ -425,10 +124,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_shownWeekCount
*/
public void setShownWeekCount(int count) {
- if (mShownWeekCount != count) {
- mShownWeekCount = count;
- invalidate();
- }
+ mDelegate.setShownWeekCount(count);
}
/**
@@ -439,7 +135,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_shownWeekCount
*/
public int getShownWeekCount() {
- return mShownWeekCount;
+ return mDelegate.getShownWeekCount();
}
/**
@@ -450,16 +146,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
*/
public void setSelectedWeekBackgroundColor(int color) {
- if (mSelectedWeekBackgroundColor != color) {
- mSelectedWeekBackgroundColor = color;
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- WeekView weekView = (WeekView) mListView.getChildAt(i);
- if (weekView.mHasSelectedDay) {
- weekView.invalidate();
- }
- }
- }
+ mDelegate.setSelectedWeekBackgroundColor(color);
}
/**
@@ -470,7 +157,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
*/
public int getSelectedWeekBackgroundColor() {
- return mSelectedWeekBackgroundColor;
+ return mDelegate.getSelectedWeekBackgroundColor();
}
/**
@@ -481,16 +168,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
*/
public void setFocusedMonthDateColor(int color) {
- if (mFocusedMonthDateColor != color) {
- mFocusedMonthDateColor = color;
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- WeekView weekView = (WeekView) mListView.getChildAt(i);
- if (weekView.mHasFocusedDay) {
- weekView.invalidate();
- }
- }
- }
+ mDelegate.setFocusedMonthDateColor(color);
}
/**
@@ -501,7 +179,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
*/
public int getFocusedMonthDateColor() {
- return mFocusedMonthDateColor;
+ return mDelegate.getFocusedMonthDateColor();
}
/**
@@ -512,16 +190,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
*/
public void setUnfocusedMonthDateColor(int color) {
- if (mUnfocusedMonthDateColor != color) {
- mUnfocusedMonthDateColor = color;
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- WeekView weekView = (WeekView) mListView.getChildAt(i);
- if (weekView.mHasUnfocusedDay) {
- weekView.invalidate();
- }
- }
- }
+ mDelegate.setUnfocusedMonthDateColor(color);
}
/**
@@ -532,7 +201,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
*/
public int getUnfocusedMonthDateColor() {
- return mFocusedMonthDateColor;
+ return mDelegate.getUnfocusedMonthDateColor();
}
/**
@@ -543,12 +212,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_weekNumberColor
*/
public void setWeekNumberColor(int color) {
- if (mWeekNumberColor != color) {
- mWeekNumberColor = color;
- if (mShowWeekNumber) {
- invalidateAllWeekViews();
- }
- }
+ mDelegate.setWeekNumberColor(color);
}
/**
@@ -559,7 +223,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_weekNumberColor
*/
public int getWeekNumberColor() {
- return mWeekNumberColor;
+ return mDelegate.getWeekNumberColor();
}
/**
@@ -570,10 +234,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
*/
public void setWeekSeparatorLineColor(int color) {
- if (mWeekSeparatorLineColor != color) {
- mWeekSeparatorLineColor = color;
- invalidateAllWeekViews();
- }
+ mDelegate.setWeekSeparatorLineColor(color);
}
/**
@@ -584,7 +245,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
*/
public int getWeekSeparatorLineColor() {
- return mWeekSeparatorLineColor;
+ return mDelegate.getWeekSeparatorLineColor();
}
/**
@@ -596,8 +257,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
*/
public void setSelectedDateVerticalBar(int resourceId) {
- Drawable drawable = getResources().getDrawable(resourceId);
- setSelectedDateVerticalBar(drawable);
+ mDelegate.setSelectedDateVerticalBar(resourceId);
}
/**
@@ -609,16 +269,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
*/
public void setSelectedDateVerticalBar(Drawable drawable) {
- if (mSelectedDateVerticalBar != drawable) {
- mSelectedDateVerticalBar = drawable;
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- WeekView weekView = (WeekView) mListView.getChildAt(i);
- if (weekView.mHasSelectedDay) {
- weekView.invalidate();
- }
- }
- }
+ mDelegate.setSelectedDateVerticalBar(drawable);
}
/**
@@ -628,7 +279,7 @@ public class CalendarView extends FrameLayout {
* @return The vertical bar drawable.
*/
public Drawable getSelectedDateVerticalBar() {
- return mSelectedDateVerticalBar;
+ return mDelegate.getSelectedDateVerticalBar();
}
/**
@@ -639,10 +290,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
*/
public void setWeekDayTextAppearance(int resourceId) {
- if (mWeekDayTextAppearanceResId != resourceId) {
- mWeekDayTextAppearanceResId = resourceId;
- setUpHeader();
- }
+ mDelegate.setWeekDayTextAppearance(resourceId);
}
/**
@@ -653,7 +301,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
*/
public int getWeekDayTextAppearance() {
- return mWeekDayTextAppearanceResId;
+ return mDelegate.getWeekDayTextAppearance();
}
/**
@@ -664,11 +312,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_dateTextAppearance
*/
public void setDateTextAppearance(int resourceId) {
- if (mDateTextAppearanceResId != resourceId) {
- mDateTextAppearanceResId = resourceId;
- updateDateTextSize();
- invalidateAllWeekViews();
- }
+ mDelegate.setDateTextAppearance(resourceId);
}
/**
@@ -679,35 +323,17 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_dateTextAppearance
*/
public int getDateTextAppearance() {
- return mDateTextAppearanceResId;
+ return mDelegate.getDateTextAppearance();
}
@Override
public void setEnabled(boolean enabled) {
- mListView.setEnabled(enabled);
+ mDelegate.setEnabled(enabled);
}
@Override
public boolean isEnabled() {
- return mListView.isEnabled();
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- setCurrentLocale(newConfig.locale);
- }
-
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(CalendarView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(CalendarView.class.getName());
+ return mDelegate.isEnabled();
}
/**
@@ -723,7 +349,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_minDate
*/
public long getMinDate() {
- return mMinDate.getTimeInMillis();
+ return mDelegate.getMinDate();
}
/**
@@ -736,30 +362,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_minDate
*/
public void setMinDate(long minDate) {
- mTempDate.setTimeInMillis(minDate);
- if (isSameDate(mTempDate, mMinDate)) {
- return;
- }
- mMinDate.setTimeInMillis(minDate);
- // make sure the current date is not earlier than
- // the new min date since the latter is used for
- // calculating the indices in the adapter thus
- // avoiding out of bounds error
- Calendar date = mAdapter.mSelectedDate;
- if (date.before(mMinDate)) {
- mAdapter.setSelectedDay(mMinDate);
- }
- // reinitialize the adapter since its range depends on min date
- mAdapter.init();
- if (date.before(mMinDate)) {
- setDate(mTempDate.getTimeInMillis());
- } else {
- // we go to the current date to force the ListView to query its
- // adapter for the shown views since we have changed the adapter
- // range and the base from which the later calculates item indices
- // note that calling setDate will not work since the date is the same
- goTo(date, false, true, false);
- }
+ mDelegate.setMinDate(minDate);
}
/**
@@ -775,7 +378,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_maxDate
*/
public long getMaxDate() {
- return mMaxDate.getTimeInMillis();
+ return mDelegate.getMaxDate();
}
/**
@@ -788,23 +391,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_maxDate
*/
public void setMaxDate(long maxDate) {
- mTempDate.setTimeInMillis(maxDate);
- if (isSameDate(mTempDate, mMaxDate)) {
- return;
- }
- mMaxDate.setTimeInMillis(maxDate);
- // reinitialize the adapter since its range depends on max date
- mAdapter.init();
- Calendar date = mAdapter.mSelectedDate;
- if (date.after(mMaxDate)) {
- setDate(mMaxDate.getTimeInMillis());
- } else {
- // we go to the current date to force the ListView to query its
- // adapter for the shown views since we have changed the adapter
- // range and the base from which the later calculates item indices
- // note that calling setDate will not work since the date is the same
- goTo(date, false, true, false);
- }
+ mDelegate.setMaxDate(maxDate);
}
/**
@@ -815,12 +402,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_showWeekNumber
*/
public void setShowWeekNumber(boolean showWeekNumber) {
- if (mShowWeekNumber == showWeekNumber) {
- return;
- }
- mShowWeekNumber = showWeekNumber;
- mAdapter.notifyDataSetChanged();
- setUpHeader();
+ mDelegate.setShowWeekNumber(showWeekNumber);
}
/**
@@ -831,7 +413,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_showWeekNumber
*/
public boolean getShowWeekNumber() {
- return mShowWeekNumber;
+ return mDelegate.getShowWeekNumber();
}
/**
@@ -850,7 +432,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_firstDayOfWeek
*/
public int getFirstDayOfWeek() {
- return mFirstDayOfWeek;
+ return mDelegate.getFirstDayOfWeek();
}
/**
@@ -869,12 +451,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_firstDayOfWeek
*/
public void setFirstDayOfWeek(int firstDayOfWeek) {
- if (mFirstDayOfWeek == firstDayOfWeek) {
- return;
- }
- mFirstDayOfWeek = firstDayOfWeek;
- mAdapter.init();
- setUpHeader();
+ mDelegate.setFirstDayOfWeek(firstDayOfWeek);
}
/**
@@ -883,7 +460,7 @@ public class CalendarView extends FrameLayout {
* @param listener The listener to be notified.
*/
public void setOnDateChangeListener(OnDateChangeListener listener) {
- mOnDateChangeListener = listener;
+ mDelegate.setOnDateChangeListener(listener);
}
/**
@@ -893,7 +470,7 @@ public class CalendarView extends FrameLayout {
* @return The selected date.
*/
public long getDate() {
- return mAdapter.mSelectedDate.getTimeInMillis();
+ return mDelegate.getDate();
}
/**
@@ -910,7 +487,7 @@ public class CalendarView extends FrameLayout {
* @see #setMaxDate(long)
*/
public void setDate(long date) {
- setDate(date, false, false);
+ mDelegate.setDate(date);
}
/**
@@ -928,937 +505,1648 @@ public class CalendarView extends FrameLayout {
* @see #setMaxDate(long)
*/
public void setDate(long date, boolean animate, boolean center) {
- mTempDate.setTimeInMillis(date);
- if (isSameDate(mTempDate, mAdapter.mSelectedDate)) {
- return;
- }
- goTo(mTempDate, animate, true, center);
+ mDelegate.setDate(date, animate, center);
}
- private void updateDateTextSize() {
- TypedArray dateTextAppearance = mContext.obtainStyledAttributes(
- mDateTextAppearanceResId, R.styleable.TextAppearance);
- mDateTextSize = dateTextAppearance.getDimensionPixelSize(
- R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE);
- dateTextAppearance.recycle();
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDelegate.onConfigurationChanged(newConfig);
}
- /**
- * Invalidates all week views.
- */
- private void invalidateAllWeekViews() {
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View view = mListView.getChildAt(i);
- view.invalidate();
- }
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ mDelegate.onInitializeAccessibilityEvent(event);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ mDelegate.onInitializeAccessibilityNodeInfo(info);
}
/**
- * Sets the current locale.
- *
- * @param locale The current locale.
+ * A delegate interface that defined the public API of the CalendarView. Allows different
+ * CalendarView implementations. This would need to be implemented by the CalendarView delegates
+ * for the real behavior.
*/
- private void setCurrentLocale(Locale locale) {
- if (locale.equals(mCurrentLocale)) {
- return;
- }
+ private interface CalendarViewDelegate {
+ void setShownWeekCount(int count);
+ int getShownWeekCount();
- mCurrentLocale = locale;
+ void setSelectedWeekBackgroundColor(int color);
+ int getSelectedWeekBackgroundColor();
- mTempDate = getCalendarForLocale(mTempDate, locale);
- mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale);
- mMinDate = getCalendarForLocale(mMinDate, locale);
- mMaxDate = getCalendarForLocale(mMaxDate, locale);
- }
+ void setFocusedMonthDateColor(int color);
+ int getFocusedMonthDateColor();
- /**
- * Gets a calendar for locale bootstrapped with the value of a given calendar.
- *
- * @param oldCalendar The old calendar.
- * @param locale The locale.
- */
- private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
- if (oldCalendar == null) {
- return Calendar.getInstance(locale);
- } else {
- final long currentTimeMillis = oldCalendar.getTimeInMillis();
- Calendar newCalendar = Calendar.getInstance(locale);
- newCalendar.setTimeInMillis(currentTimeMillis);
- return newCalendar;
- }
- }
+ void setUnfocusedMonthDateColor(int color);
+ int getUnfocusedMonthDateColor();
- /**
- * @return True if the <code>firstDate</code> is the same as the <code>
- * secondDate</code>.
- */
- private boolean isSameDate(Calendar firstDate, Calendar secondDate) {
- return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR)
- && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR));
- }
+ void setWeekNumberColor(int color);
+ int getWeekNumberColor();
- /**
- * Creates a new adapter if necessary and sets up its parameters.
- */
- private void setUpAdapter() {
- if (mAdapter == null) {
- mAdapter = new WeeksAdapter();
- mAdapter.registerDataSetObserver(new DataSetObserver() {
- @Override
- public void onChanged() {
- if (mOnDateChangeListener != null) {
- Calendar selectedDay = mAdapter.getSelectedDay();
- mOnDateChangeListener.onSelectedDayChange(CalendarView.this,
- selectedDay.get(Calendar.YEAR),
- selectedDay.get(Calendar.MONTH),
- selectedDay.get(Calendar.DAY_OF_MONTH));
- }
- }
- });
- mListView.setAdapter(mAdapter);
- }
+ void setWeekSeparatorLineColor(int color);
+ int getWeekSeparatorLineColor();
- // refresh the view with the new parameters
- mAdapter.notifyDataSetChanged();
+ void setSelectedDateVerticalBar(int resourceId);
+ void setSelectedDateVerticalBar(Drawable drawable);
+ Drawable getSelectedDateVerticalBar();
+
+ void setWeekDayTextAppearance(int resourceId);
+ int getWeekDayTextAppearance();
+
+ void setDateTextAppearance(int resourceId);
+ int getDateTextAppearance();
+
+ void setEnabled(boolean enabled);
+ boolean isEnabled();
+
+ void setMinDate(long minDate);
+ long getMinDate();
+
+ void setMaxDate(long maxDate);
+ long getMaxDate();
+
+ void setShowWeekNumber(boolean showWeekNumber);
+ boolean getShowWeekNumber();
+
+ void setFirstDayOfWeek(int firstDayOfWeek);
+ int getFirstDayOfWeek();
+
+ void setDate(long date);
+ void setDate(long date, boolean animate, boolean center);
+ long getDate();
+
+ void setOnDateChangeListener(OnDateChangeListener listener);
+
+ void onConfigurationChanged(Configuration newConfig);
+ void onInitializeAccessibilityEvent(AccessibilityEvent event);
+ void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
}
/**
- * Sets up the strings to be used by the header.
+ * An abstract class which can be used as a start for CalendarView implementations
*/
- private void setUpHeader() {
- final String[] tinyWeekdayNames = LocaleData.get(Locale.getDefault()).tinyWeekdayNames;
- mDayLabels = new String[mDaysPerWeek];
- for (int i = 0; i < mDaysPerWeek; i++) {
- final int j = i + mFirstDayOfWeek;
- final int calendarDay = (j > Calendar.SATURDAY) ? j - Calendar.SATURDAY : j;
- mDayLabels[i] = tinyWeekdayNames[calendarDay];
- }
- // Deal with week number
- TextView label = (TextView) mDayNamesHeader.getChildAt(0);
- if (mShowWeekNumber) {
- label.setVisibility(View.VISIBLE);
- } else {
- label.setVisibility(View.GONE);
+ abstract static class AbstractCalendarViewDelegate implements CalendarViewDelegate {
+ // The delegator
+ protected CalendarView mDelegator;
+
+ // The context
+ protected Context mContext;
+
+ // The current locale
+ protected Locale mCurrentLocale;
+
+ AbstractCalendarViewDelegate(CalendarView delegator, Context context) {
+ mDelegator = delegator;
+ mContext = context;
+
+ // Initialization based on locale
+ setCurrentLocale(Locale.getDefault());
}
- // Deal with day labels
- final int count = mDayNamesHeader.getChildCount();
- for (int i = 0; i < count - 1; i++) {
- label = (TextView) mDayNamesHeader.getChildAt(i + 1);
- if (mWeekDayTextAppearanceResId > -1) {
- label.setTextAppearance(mContext, mWeekDayTextAppearanceResId);
- }
- if (i < mDaysPerWeek) {
- label.setText(mDayLabels[i]);
- label.setVisibility(View.VISIBLE);
- } else {
- label.setVisibility(View.GONE);
+
+ protected void setCurrentLocale(Locale locale) {
+ if (locale.equals(mCurrentLocale)) {
+ return;
}
+ mCurrentLocale = locale;
}
- mDayNamesHeader.invalidate();
}
/**
- * Sets all the required fields for the list view.
+ * A delegate implementing the legacy CalendarView
*/
- private void setUpListView() {
- // Configure the listview
- mListView.setDivider(null);
- mListView.setItemsCanFocus(true);
- mListView.setVerticalScrollBarEnabled(false);
- mListView.setOnScrollListener(new OnScrollListener() {
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- CalendarView.this.onScrollStateChanged(view, scrollState);
- }
-
- public void onScroll(
- AbsListView view, int firstVisibleItem, int visibleItemCount,
- int totalItemCount) {
- CalendarView.this.onScroll(view, firstVisibleItem, visibleItemCount,
- totalItemCount);
- }
- });
- // Make the scrolling behavior nicer
- mListView.setFriction(mFriction);
- mListView.setVelocityScale(mVelocityScale);
- }
+ private static class LegacyCalendarViewDelegate extends AbstractCalendarViewDelegate {
- /**
- * This moves to the specified time in the view. If the time is not already
- * in range it will move the list so that the first of the month containing
- * the time is at the top of the view. If the new time is already in view
- * the list will not be scrolled unless forceScroll is true. This time may
- * optionally be highlighted as selected as well.
- *
- * @param date The time to move to.
- * @param animate Whether to scroll to the given time or just redraw at the
- * new location.
- * @param setSelected Whether to set the given time as selected.
- * @param forceScroll Whether to recenter even if the time is already
- * visible.
- *
- * @throws IllegalArgumentException of the provided date is before the
- * range start of after the range end.
- */
- private void goTo(Calendar date, boolean animate, boolean setSelected, boolean forceScroll) {
- if (date.before(mMinDate) || date.after(mMaxDate)) {
- throw new IllegalArgumentException("Time not between " + mMinDate.getTime()
- + " and " + mMaxDate.getTime());
- }
- // Find the first and last entirely visible weeks
- int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
- View firstChild = mListView.getChildAt(0);
- if (firstChild != null && firstChild.getTop() < 0) {
- firstFullyVisiblePosition++;
- }
- int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1;
- if (firstChild != null && firstChild.getTop() > mBottomBuffer) {
- lastFullyVisiblePosition--;
- }
- if (setSelected) {
- mAdapter.setSelectedDay(date);
- }
- // Get the week we're going to
- int position = getWeeksSinceMinDate(date);
+ /**
+ * Default value whether to show week number.
+ */
+ private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true;
- // Check if the selected day is now outside of our visible range
- // and if so scroll to the month that contains it
- if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition
- || forceScroll) {
- mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis());
- mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1);
+ /**
+ * The number of milliseconds in a day.e
+ */
+ private static final long MILLIS_IN_DAY = 86400000L;
- setMonthDisplayed(mFirstDayOfMonth);
+ /**
+ * The number of day in a week.
+ */
+ private static final int DAYS_PER_WEEK = 7;
- // the earliest time we can scroll to is the min date
- if (mFirstDayOfMonth.before(mMinDate)) {
- position = 0;
- } else {
- position = getWeeksSinceMinDate(mFirstDayOfMonth);
- }
+ /**
+ * The number of milliseconds in a week.
+ */
+ private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY;
- mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
- if (animate) {
- mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset,
- GOTO_SCROLL_DURATION);
- } else {
- mListView.setSelectionFromTop(position, mListScrollTopOffset);
- // Perform any after scroll operations that are needed
- onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE);
- }
- } else if (setSelected) {
- // Otherwise just set the selection
- setMonthDisplayed(date);
- }
- }
+ /**
+ * Affects when the month selection will change while scrolling upe
+ */
+ private static final int SCROLL_HYST_WEEKS = 2;
- /**
- * Parses the given <code>date</code> and in case of success sets
- * the result to the <code>outDate</code>.
- *
- * @return True if the date was parsed.
- */
- private boolean parseDate(String date, Calendar outDate) {
- try {
- outDate.setTime(mDateFormat.parse(date));
- return true;
- } catch (ParseException e) {
- Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
- return false;
- }
- }
+ /**
+ * How long the GoTo fling animation should last.
+ */
+ private static final int GOTO_SCROLL_DURATION = 1000;
- /**
- * Called when a <code>view</code> transitions to a new <code>scrollState
- * </code>.
- */
- private void onScrollStateChanged(AbsListView view, int scrollState) {
- mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
- }
+ /**
+ * The duration of the adjustment upon a user scroll in milliseconds.
+ */
+ private static final int ADJUSTMENT_SCROLL_DURATION = 500;
- /**
- * Updates the title and selected month if the <code>view</code> has moved to a new
- * month.
- */
- private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
- int totalItemCount) {
- WeekView child = (WeekView) view.getChildAt(0);
- if (child == null) {
- return;
- }
+ /**
+ * How long to wait after receiving an onScrollStateChanged notification
+ * before acting on it.
+ */
+ private static final int SCROLL_CHANGE_DELAY = 40;
- // Figure out where we are
- long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
+ /**
+ * String for parsing dates.
+ */
+ private static final String DATE_FORMAT = "MM/dd/yyyy";
- // If we have moved since our last call update the direction
- if (currScroll < mPreviousScrollPosition) {
- mIsScrollingUp = true;
- } else if (currScroll > mPreviousScrollPosition) {
- mIsScrollingUp = false;
- } else {
- return;
- }
+ /**
+ * The default minimal date.
+ */
+ private static final String DEFAULT_MIN_DATE = "01/01/1900";
- // Use some hysteresis for checking which month to highlight. This
- // causes the month to transition when two full weeks of a month are
- // visible when scrolling up, and when the first day in a month reaches
- // the top of the screen when scrolling down.
- int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0;
- if (mIsScrollingUp) {
- child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset);
- } else if (offset != 0) {
- child = (WeekView) view.getChildAt(offset);
- }
+ /**
+ * The default maximal date.
+ */
+ private static final String DEFAULT_MAX_DATE = "01/01/2100";
- if (child != null) {
- // Find out which month we're moving into
- int month;
- if (mIsScrollingUp) {
- month = child.getMonthOfFirstWeekDay();
- } else {
- month = child.getMonthOfLastWeekDay();
- }
+ private static final int DEFAULT_SHOWN_WEEK_COUNT = 6;
- // And how it relates to our current highlighted month
- int monthDiff;
- if (mCurrentMonthDisplayed == 11 && month == 0) {
- monthDiff = 1;
- } else if (mCurrentMonthDisplayed == 0 && month == 11) {
- monthDiff = -1;
- } else {
- monthDiff = month - mCurrentMonthDisplayed;
- }
+ private static final int DEFAULT_DATE_TEXT_SIZE = 14;
- // Only switch months if we're scrolling away from the currently
- // selected month
- if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) {
- Calendar firstDay = child.getFirstDay();
- if (mIsScrollingUp) {
- firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK);
- } else {
- firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK);
- }
- setMonthDisplayed(firstDay);
- }
- }
+ private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6;
- mPreviousScrollPosition = currScroll;
- mPreviousScrollState = mCurrentScrollState;
- }
+ private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12;
- /**
- * Sets the month displayed at the top of this view based on time. Override
- * to add custom events when the title is changed.
- *
- * @param calendar A day in the new focus month.
- */
- private void setMonthDisplayed(Calendar calendar) {
- mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
- mAdapter.setFocusMonth(mCurrentMonthDisplayed);
- final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
- | DateUtils.FORMAT_SHOW_YEAR;
- final long millis = calendar.getTimeInMillis();
- String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
- mMonthName.setText(newMonthName);
- mMonthName.invalidate();
- }
+ private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2;
- /**
- * @return Returns the number of weeks between the current <code>date</code>
- * and the <code>mMinDate</code>.
- */
- private int getWeeksSinceMinDate(Calendar date) {
- if (date.before(mMinDate)) {
- throw new IllegalArgumentException("fromDate: " + mMinDate.getTime()
- + " does not precede toDate: " + date.getTime());
- }
- long endTimeMillis = date.getTimeInMillis()
- + date.getTimeZone().getOffset(date.getTimeInMillis());
- long startTimeMillis = mMinDate.getTimeInMillis()
- + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis());
- long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek)
- * MILLIS_IN_DAY;
- return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK);
- }
+ private static final int UNSCALED_BOTTOM_BUFFER = 20;
- /**
- * Command responsible for acting upon scroll state changes.
- */
- private class ScrollStateRunnable implements Runnable {
- private AbsListView mView;
+ private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1;
+
+ private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1;
+
+ private final int mWeekSeperatorLineWidth;
+
+ private int mDateTextSize;
+
+ private Drawable mSelectedDateVerticalBar;
+
+ private final int mSelectedDateVerticalBarWidth;
+
+ private int mSelectedWeekBackgroundColor;
+
+ private int mFocusedMonthDateColor;
- private int mNewState;
+ private int mUnfocusedMonthDateColor;
+
+ private int mWeekSeparatorLineColor;
+
+ private int mWeekNumberColor;
+
+ private int mWeekDayTextAppearanceResId;
+
+ private int mDateTextAppearanceResId;
/**
- * Sets up the runnable with a short delay in case the scroll state
- * immediately changes again.
- *
- * @param view The list view that changed state
- * @param scrollState The new state it changed to
+ * The top offset of the weeks list.
*/
- public void doScrollStateChange(AbsListView view, int scrollState) {
- mView = view;
- mNewState = scrollState;
- removeCallbacks(this);
- postDelayed(this, SCROLL_CHANGE_DELAY);
- }
+ private int mListScrollTopOffset = 2;
- public void run() {
- mCurrentScrollState = mNewState;
- // Fix the position after a scroll or a fling ends
- if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
- && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
- View child = mView.getChildAt(0);
- if (child == null) {
- // The view is no longer visible, just return
- return;
- }
- int dist = child.getBottom() - mListScrollTopOffset;
- if (dist > mListScrollTopOffset) {
- if (mIsScrollingUp) {
- mView.smoothScrollBy(dist - child.getHeight(), ADJUSTMENT_SCROLL_DURATION);
- } else {
- mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION);
- }
- }
- }
- mPreviousScrollState = mNewState;
- }
- }
+ /**
+ * The visible height of a week view.
+ */
+ private int mWeekMinVisibleHeight = 12;
- /**
- * <p>
- * This is a specialized adapter for creating a list of weeks with
- * selectable days. It can be configured to display the week number, start
- * the week on a given day, show a reduced number of days, or display an
- * arbitrary number of weeks at a time.
- * </p>
- */
- private class WeeksAdapter extends BaseAdapter implements OnTouchListener {
- private final Calendar mSelectedDate = Calendar.getInstance();
- private final GestureDetector mGestureDetector;
+ /**
+ * The visible height of a week view.
+ */
+ private int mBottomBuffer = 20;
- private int mSelectedWeek;
+ /**
+ * The number of shown weeks.
+ */
+ private int mShownWeekCount;
- private int mFocusedMonth;
+ /**
+ * Flag whether to show the week number.
+ */
+ private boolean mShowWeekNumber;
- private int mTotalWeekCount;
+ /**
+ * The number of day per week to be shown.
+ */
+ private int mDaysPerWeek = 7;
- public WeeksAdapter() {
- mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
- init();
- }
+ /**
+ * The friction of the week list while flinging.
+ */
+ private float mFriction = .05f;
/**
- * Set up the gesture detector and selected time
+ * Scale for adjusting velocity of the week list while flinging.
*/
- private void init() {
- mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
- mTotalWeekCount = getWeeksSinceMinDate(mMaxDate);
- if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek
- || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) {
- mTotalWeekCount++;
- }
- notifyDataSetChanged();
- }
+ private float mVelocityScale = 0.333f;
/**
- * Updates the selected day and related parameters.
- *
- * @param selectedDay The time to highlight
+ * The adapter for the weeks list.
*/
- public void setSelectedDay(Calendar selectedDay) {
- if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR)
- && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) {
- return;
- }
- mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis());
- mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
- mFocusedMonth = mSelectedDate.get(Calendar.MONTH);
- notifyDataSetChanged();
- }
+ private WeeksAdapter mAdapter;
/**
- * @return The selected day of month.
+ * The weeks list.
*/
- public Calendar getSelectedDay() {
- return mSelectedDate;
+ private ListView mListView;
+
+ /**
+ * The name of the month to display.
+ */
+ private TextView mMonthName;
+
+ /**
+ * The header with week day names.
+ */
+ private ViewGroup mDayNamesHeader;
+
+ /**
+ * Cached labels for the week names header.
+ */
+ private String[] mDayLabels;
+
+ /**
+ * The first day of the week.
+ */
+ private int mFirstDayOfWeek;
+
+ /**
+ * Which month should be displayed/highlighted [0-11].
+ */
+ private int mCurrentMonthDisplayed = -1;
+
+ /**
+ * Used for tracking during a scroll.
+ */
+ private long mPreviousScrollPosition;
+
+ /**
+ * Used for tracking which direction the view is scrolling.
+ */
+ private boolean mIsScrollingUp = false;
+
+ /**
+ * The previous scroll state of the weeks ListView.
+ */
+ private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+
+ /**
+ * The current scroll state of the weeks ListView.
+ */
+ private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+
+ /**
+ * Listener for changes in the selected day.
+ */
+ private OnDateChangeListener mOnDateChangeListener;
+
+ /**
+ * Command for adjusting the position after a scroll/fling.
+ */
+ private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
+
+ /**
+ * Temporary instance to avoid multiple instantiations.
+ */
+ private Calendar mTempDate;
+
+ /**
+ * The first day of the focused month.
+ */
+ private Calendar mFirstDayOfMonth;
+
+ /**
+ * The start date of the range supported by this picker.
+ */
+ private Calendar mMinDate;
+
+ /**
+ * The end date of the range supported by this picker.
+ */
+ private Calendar mMaxDate;
+
+ /**
+ * Date format for parsing dates.
+ */
+ private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
+
+ LegacyCalendarViewDelegate(CalendarView delegator, Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(delegator, context);
+
+ // initialization based on locale
+ setCurrentLocale(Locale.getDefault());
+
+ TypedArray attributesArray = context.obtainStyledAttributes(attrs,
+ R.styleable.CalendarView, defStyleAttr, defStyleRes);
+ mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber,
+ DEFAULT_SHOW_WEEK_NUMBER);
+ mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek,
+ LocaleData.get(Locale.getDefault()).firstDayOfWeek);
+ String minDate = attributesArray.getString(R.styleable.CalendarView_minDate);
+ if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) {
+ parseDate(DEFAULT_MIN_DATE, mMinDate);
+ }
+ String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate);
+ if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) {
+ parseDate(DEFAULT_MAX_DATE, mMaxDate);
+ }
+ if (mMaxDate.before(mMinDate)) {
+ throw new IllegalArgumentException("Max date cannot be before min date.");
+ }
+ mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount,
+ DEFAULT_SHOWN_WEEK_COUNT);
+ mSelectedWeekBackgroundColor = attributesArray.getColor(
+ R.styleable.CalendarView_selectedWeekBackgroundColor, 0);
+ mFocusedMonthDateColor = attributesArray.getColor(
+ R.styleable.CalendarView_focusedMonthDateColor, 0);
+ mUnfocusedMonthDateColor = attributesArray.getColor(
+ R.styleable.CalendarView_unfocusedMonthDateColor, 0);
+ mWeekSeparatorLineColor = attributesArray.getColor(
+ R.styleable.CalendarView_weekSeparatorLineColor, 0);
+ mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0);
+ mSelectedDateVerticalBar = attributesArray.getDrawable(
+ R.styleable.CalendarView_selectedDateVerticalBar);
+
+ mDateTextAppearanceResId = attributesArray.getResourceId(
+ R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small);
+ updateDateTextSize();
+
+ mWeekDayTextAppearanceResId = attributesArray.getResourceId(
+ R.styleable.CalendarView_weekDayTextAppearance,
+ DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID);
+ attributesArray.recycle();
+
+ DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics();
+ mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics);
+ mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics);
+ mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_BOTTOM_BUFFER, displayMetrics);
+ mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics);
+ mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics);
+
+ LayoutInflater layoutInflater = (LayoutInflater) mContext
+ .getSystemService(Service.LAYOUT_INFLATER_SERVICE);
+ View content = layoutInflater.inflate(R.layout.calendar_view, null, false);
+ mDelegator.addView(content);
+
+ mListView = (ListView) mDelegator.findViewById(R.id.list);
+ mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names);
+ mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name);
+
+ setUpHeader();
+ setUpListView();
+ setUpAdapter();
+
+ // go to today or whichever is close to today min or max date
+ mTempDate.setTimeInMillis(System.currentTimeMillis());
+ if (mTempDate.before(mMinDate)) {
+ goTo(mMinDate, false, true, true);
+ } else if (mMaxDate.before(mTempDate)) {
+ goTo(mMaxDate, false, true, true);
+ } else {
+ goTo(mTempDate, false, true, true);
+ }
+
+ mDelegator.invalidate();
}
@Override
- public int getCount() {
- return mTotalWeekCount;
+ public void setShownWeekCount(int count) {
+ if (mShownWeekCount != count) {
+ mShownWeekCount = count;
+ mDelegator.invalidate();
+ }
}
@Override
- public Object getItem(int position) {
- return null;
+ public int getShownWeekCount() {
+ return mShownWeekCount;
}
@Override
- public long getItemId(int position) {
- return position;
+ public void setSelectedWeekBackgroundColor(int color) {
+ if (mSelectedWeekBackgroundColor != color) {
+ mSelectedWeekBackgroundColor = color;
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ WeekView weekView = (WeekView) mListView.getChildAt(i);
+ if (weekView.mHasSelectedDay) {
+ weekView.invalidate();
+ }
+ }
+ }
}
@Override
- public View getView(int position, View convertView, ViewGroup parent) {
- WeekView weekView = null;
- if (convertView != null) {
- weekView = (WeekView) convertView;
- } else {
- weekView = new WeekView(mContext);
- android.widget.AbsListView.LayoutParams params =
- new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT);
- weekView.setLayoutParams(params);
- weekView.setClickable(true);
- weekView.setOnTouchListener(this);
- }
+ public int getSelectedWeekBackgroundColor() {
+ return mSelectedWeekBackgroundColor;
+ }
- int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
- Calendar.DAY_OF_WEEK) : -1;
- weekView.init(position, selectedWeekDay, mFocusedMonth);
+ @Override
+ public void setFocusedMonthDateColor(int color) {
+ if (mFocusedMonthDateColor != color) {
+ mFocusedMonthDateColor = color;
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ WeekView weekView = (WeekView) mListView.getChildAt(i);
+ if (weekView.mHasFocusedDay) {
+ weekView.invalidate();
+ }
+ }
+ }
+ }
- return weekView;
+ @Override
+ public int getFocusedMonthDateColor() {
+ return mFocusedMonthDateColor;
}
- /**
- * Changes which month is in focus and updates the view.
- *
- * @param month The month to show as in focus [0-11]
- */
- public void setFocusMonth(int month) {
- if (mFocusedMonth == month) {
- return;
+ @Override
+ public void setUnfocusedMonthDateColor(int color) {
+ if (mUnfocusedMonthDateColor != color) {
+ mUnfocusedMonthDateColor = color;
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ WeekView weekView = (WeekView) mListView.getChildAt(i);
+ if (weekView.mHasUnfocusedDay) {
+ weekView.invalidate();
+ }
+ }
}
- mFocusedMonth = month;
- notifyDataSetChanged();
}
@Override
- public boolean onTouch(View v, MotionEvent event) {
- if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) {
- WeekView weekView = (WeekView) v;
- // if we cannot find a day for the given location we are done
- if (!weekView.getDayFromLocation(event.getX(), mTempDate)) {
- return true;
- }
- // it is possible that the touched day is outside the valid range
- // we draw whole weeks but range end can fall not on the week end
- if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
- return true;
+ public int getUnfocusedMonthDateColor() {
+ return mFocusedMonthDateColor;
+ }
+
+ @Override
+ public void setWeekNumberColor(int color) {
+ if (mWeekNumberColor != color) {
+ mWeekNumberColor = color;
+ if (mShowWeekNumber) {
+ invalidateAllWeekViews();
}
- onDateTapped(mTempDate);
- return true;
}
- return false;
}
- /**
- * Maintains the same hour/min/sec but moves the day to the tapped day.
- *
- * @param day The day that was tapped
- */
- private void onDateTapped(Calendar day) {
- setSelectedDay(day);
- setMonthDisplayed(day);
+ @Override
+ public int getWeekNumberColor() {
+ return mWeekNumberColor;
}
- /**
- * This is here so we can identify single tap events and set the
- * selected day correctly
- */
- class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- return true;
+ @Override
+ public void setWeekSeparatorLineColor(int color) {
+ if (mWeekSeparatorLineColor != color) {
+ mWeekSeparatorLineColor = color;
+ invalidateAllWeekViews();
}
}
- }
- /**
- * <p>
- * This is a dynamic view for drawing a single week. It can be configured to
- * display the week number, start the week on a given day, or show a reduced
- * number of days. It is intended for use as a single view within a
- * ListView. See {@link WeeksAdapter} for usage.
- * </p>
- */
- private class WeekView extends View {
+ @Override
+ public int getWeekSeparatorLineColor() {
+ return mWeekSeparatorLineColor;
+ }
- private final Rect mTempRect = new Rect();
+ @Override
+ public void setSelectedDateVerticalBar(int resourceId) {
+ Drawable drawable = mDelegator.getContext().getDrawable(resourceId);
+ setSelectedDateVerticalBar(drawable);
+ }
- private final Paint mDrawPaint = new Paint();
+ @Override
+ public void setSelectedDateVerticalBar(Drawable drawable) {
+ if (mSelectedDateVerticalBar != drawable) {
+ mSelectedDateVerticalBar = drawable;
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ WeekView weekView = (WeekView) mListView.getChildAt(i);
+ if (weekView.mHasSelectedDay) {
+ weekView.invalidate();
+ }
+ }
+ }
+ }
- private final Paint mMonthNumDrawPaint = new Paint();
+ @Override
+ public Drawable getSelectedDateVerticalBar() {
+ return mSelectedDateVerticalBar;
+ }
- // Cache the number strings so we don't have to recompute them each time
- private String[] mDayNumbers;
+ @Override
+ public void setWeekDayTextAppearance(int resourceId) {
+ if (mWeekDayTextAppearanceResId != resourceId) {
+ mWeekDayTextAppearanceResId = resourceId;
+ setUpHeader();
+ }
+ }
+
+ @Override
+ public int getWeekDayTextAppearance() {
+ return mWeekDayTextAppearanceResId;
+ }
+
+ @Override
+ public void setDateTextAppearance(int resourceId) {
+ if (mDateTextAppearanceResId != resourceId) {
+ mDateTextAppearanceResId = resourceId;
+ updateDateTextSize();
+ invalidateAllWeekViews();
+ }
+ }
+
+ @Override
+ public int getDateTextAppearance() {
+ return mDateTextAppearanceResId;
+ }
- // Quick lookup for checking which days are in the focus month
- private boolean[] mFocusDay;
+ @Override
+ public void setEnabled(boolean enabled) {
+ mListView.setEnabled(enabled);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mListView.isEnabled();
+ }
- // Whether this view has a focused day.
- private boolean mHasFocusedDay;
+ @Override
+ public void setMinDate(long minDate) {
+ mTempDate.setTimeInMillis(minDate);
+ if (isSameDate(mTempDate, mMinDate)) {
+ return;
+ }
+ mMinDate.setTimeInMillis(minDate);
+ // make sure the current date is not earlier than
+ // the new min date since the latter is used for
+ // calculating the indices in the adapter thus
+ // avoiding out of bounds error
+ Calendar date = mAdapter.mSelectedDate;
+ if (date.before(mMinDate)) {
+ mAdapter.setSelectedDay(mMinDate);
+ }
+ // reinitialize the adapter since its range depends on min date
+ mAdapter.init();
+ if (date.before(mMinDate)) {
+ setDate(mTempDate.getTimeInMillis());
+ } else {
+ // we go to the current date to force the ListView to query its
+ // adapter for the shown views since we have changed the adapter
+ // range and the base from which the later calculates item indices
+ // note that calling setDate will not work since the date is the same
+ goTo(date, false, true, false);
+ }
+ }
- // Whether this view has only focused days.
- private boolean mHasUnfocusedDay;
+ @Override
+ public long getMinDate() {
+ return mMinDate.getTimeInMillis();
+ }
- // The first day displayed by this item
- private Calendar mFirstDay;
+ @Override
+ public void setMaxDate(long maxDate) {
+ mTempDate.setTimeInMillis(maxDate);
+ if (isSameDate(mTempDate, mMaxDate)) {
+ return;
+ }
+ mMaxDate.setTimeInMillis(maxDate);
+ // reinitialize the adapter since its range depends on max date
+ mAdapter.init();
+ Calendar date = mAdapter.mSelectedDate;
+ if (date.after(mMaxDate)) {
+ setDate(mMaxDate.getTimeInMillis());
+ } else {
+ // we go to the current date to force the ListView to query its
+ // adapter for the shown views since we have changed the adapter
+ // range and the base from which the later calculates item indices
+ // note that calling setDate will not work since the date is the same
+ goTo(date, false, true, false);
+ }
+ }
- // The month of the first day in this week
- private int mMonthOfFirstWeekDay = -1;
+ @Override
+ public long getMaxDate() {
+ return mMaxDate.getTimeInMillis();
+ }
- // The month of the last day in this week
- private int mLastWeekDayMonth = -1;
+ @Override
+ public void setShowWeekNumber(boolean showWeekNumber) {
+ if (mShowWeekNumber == showWeekNumber) {
+ return;
+ }
+ mShowWeekNumber = showWeekNumber;
+ mAdapter.notifyDataSetChanged();
+ setUpHeader();
+ }
- // The position of this week, equivalent to weeks since the week of Jan
- // 1st, 1900
- private int mWeek = -1;
+ @Override
+ public boolean getShowWeekNumber() {
+ return mShowWeekNumber;
+ }
- // Quick reference to the width of this view, matches parent
- private int mWidth;
+ @Override
+ public void setFirstDayOfWeek(int firstDayOfWeek) {
+ if (mFirstDayOfWeek == firstDayOfWeek) {
+ return;
+ }
+ mFirstDayOfWeek = firstDayOfWeek;
+ mAdapter.init();
+ mAdapter.notifyDataSetChanged();
+ setUpHeader();
+ }
- // The height this view should draw at in pixels, set by height param
- private int mHeight;
+ @Override
+ public int getFirstDayOfWeek() {
+ return mFirstDayOfWeek;
+ }
- // If this view contains the selected day
- private boolean mHasSelectedDay = false;
+ @Override
+ public void setDate(long date) {
+ setDate(date, false, false);
+ }
- // Which day is selected [0-6] or -1 if no day is selected
- private int mSelectedDay = -1;
+ @Override
+ public void setDate(long date, boolean animate, boolean center) {
+ mTempDate.setTimeInMillis(date);
+ if (isSameDate(mTempDate, mAdapter.mSelectedDate)) {
+ return;
+ }
+ goTo(mTempDate, animate, true, center);
+ }
- // The number of days + a spot for week number if it is displayed
- private int mNumCells;
+ @Override
+ public long getDate() {
+ return mAdapter.mSelectedDate.getTimeInMillis();
+ }
- // The left edge of the selected day
- private int mSelectedLeft = -1;
+ @Override
+ public void setOnDateChangeListener(OnDateChangeListener listener) {
+ mOnDateChangeListener = listener;
+ }
- // The right edge of the selected day
- private int mSelectedRight = -1;
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ setCurrentLocale(newConfig.locale);
+ }
- public WeekView(Context context) {
- super(context);
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ event.setClassName(CalendarView.class.getName());
+ }
- // Sets up any standard paints that will be used
- initilaizePaints();
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ info.setClassName(CalendarView.class.getName());
}
/**
- * Initializes this week view.
+ * Sets the current locale.
*
- * @param weekNumber The number of the week this view represents. The
- * week number is a zero based index of the weeks since
- * {@link CalendarView#getMinDate()}.
- * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no
- * selected day.
- * @param focusedMonth The month that is currently in focus i.e.
- * highlighted.
+ * @param locale The current locale.
*/
- public void init(int weekNumber, int selectedWeekDay, int focusedMonth) {
- mSelectedDay = selectedWeekDay;
- mHasSelectedDay = mSelectedDay != -1;
- mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek;
- mWeek = weekNumber;
- mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
-
- mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek);
- mTempDate.setFirstDayOfWeek(mFirstDayOfWeek);
-
- // Allocate space for caching the day numbers and focus values
- mDayNumbers = new String[mNumCells];
- mFocusDay = new boolean[mNumCells];
-
- // If we're showing the week number calculate it based on Monday
- int i = 0;
- if (mShowWeekNumber) {
- mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
- mTempDate.get(Calendar.WEEK_OF_YEAR));
- i++;
- }
-
- // Now adjust our starting day based on the start day of the week
- int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK);
- mTempDate.add(Calendar.DAY_OF_MONTH, diff);
-
- mFirstDay = (Calendar) mTempDate.clone();
- mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH);
-
- mHasUnfocusedDay = true;
- for (; i < mNumCells; i++) {
- final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth);
- mFocusDay[i] = isFocusedDay;
- mHasFocusedDay |= isFocusedDay;
- mHasUnfocusedDay &= !isFocusedDay;
- // do not draw dates outside the valid range to avoid user confusion
- if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
- mDayNumbers[i] = "";
- } else {
- mDayNumbers[i] = String.format(Locale.getDefault(), "%d",
- mTempDate.get(Calendar.DAY_OF_MONTH));
- }
- mTempDate.add(Calendar.DAY_OF_MONTH, 1);
- }
- // We do one extra add at the end of the loop, if that pushed us to
- // new month undo it
- if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) {
- mTempDate.add(Calendar.DAY_OF_MONTH, -1);
- }
- mLastWeekDayMonth = mTempDate.get(Calendar.MONTH);
+ @Override
+ protected void setCurrentLocale(Locale locale) {
+ super.setCurrentLocale(locale);
- updateSelectionPositions();
+ mTempDate = getCalendarForLocale(mTempDate, locale);
+ mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale);
+ mMinDate = getCalendarForLocale(mMinDate, locale);
+ mMaxDate = getCalendarForLocale(mMaxDate, locale);
+ }
+ private void updateDateTextSize() {
+ TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes(
+ mDateTextAppearanceResId, R.styleable.TextAppearance);
+ mDateTextSize = dateTextAppearance.getDimensionPixelSize(
+ R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE);
+ dateTextAppearance.recycle();
}
/**
- * Initialize the paint instances.
+ * Invalidates all week views.
*/
- private void initilaizePaints() {
- mDrawPaint.setFakeBoldText(false);
- mDrawPaint.setAntiAlias(true);
- mDrawPaint.setStyle(Style.FILL);
-
- mMonthNumDrawPaint.setFakeBoldText(true);
- mMonthNumDrawPaint.setAntiAlias(true);
- mMonthNumDrawPaint.setStyle(Style.FILL);
- mMonthNumDrawPaint.setTextAlign(Align.CENTER);
- mMonthNumDrawPaint.setTextSize(mDateTextSize);
+ private void invalidateAllWeekViews() {
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View view = mListView.getChildAt(i);
+ view.invalidate();
+ }
}
/**
- * Returns the month of the first day in this week.
+ * Gets a calendar for locale bootstrapped with the value of a given calendar.
*
- * @return The month the first day of this view is in.
+ * @param oldCalendar The old calendar.
+ * @param locale The locale.
*/
- public int getMonthOfFirstWeekDay() {
- return mMonthOfFirstWeekDay;
+ private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
+ if (oldCalendar == null) {
+ return Calendar.getInstance(locale);
+ } else {
+ final long currentTimeMillis = oldCalendar.getTimeInMillis();
+ Calendar newCalendar = Calendar.getInstance(locale);
+ newCalendar.setTimeInMillis(currentTimeMillis);
+ return newCalendar;
+ }
}
/**
- * Returns the month of the last day in this week
- *
- * @return The month the last day of this view is in
+ * @return True if the <code>firstDate</code> is the same as the <code>
+ * secondDate</code>.
*/
- public int getMonthOfLastWeekDay() {
- return mLastWeekDayMonth;
+ private static boolean isSameDate(Calendar firstDate, Calendar secondDate) {
+ return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR)
+ && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR));
}
/**
- * Returns the first day in this view.
- *
- * @return The first day in the view.
+ * Creates a new adapter if necessary and sets up its parameters.
*/
- public Calendar getFirstDay() {
- return mFirstDay;
+ private void setUpAdapter() {
+ if (mAdapter == null) {
+ mAdapter = new WeeksAdapter(mContext);
+ mAdapter.registerDataSetObserver(new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ if (mOnDateChangeListener != null) {
+ Calendar selectedDay = mAdapter.getSelectedDay();
+ mOnDateChangeListener.onSelectedDayChange(mDelegator,
+ selectedDay.get(Calendar.YEAR),
+ selectedDay.get(Calendar.MONTH),
+ selectedDay.get(Calendar.DAY_OF_MONTH));
+ }
+ }
+ });
+ mListView.setAdapter(mAdapter);
+ }
+
+ // refresh the view with the new parameters
+ mAdapter.notifyDataSetChanged();
}
/**
- * Calculates the day that the given x position is in, accounting for
- * week number.
- *
- * @param x The x position of the touch event.
- * @return True if a day was found for the given location.
+ * Sets up the strings to be used by the header.
*/
- public boolean getDayFromLocation(float x, Calendar outCalendar) {
- final boolean isLayoutRtl = isLayoutRtl();
-
- int start;
- int end;
+ private void setUpHeader() {
+ mDayLabels = new String[mDaysPerWeek];
+ for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) {
+ int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i;
+ mDayLabels[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
+ DateUtils.LENGTH_SHORTEST);
+ }
- if (isLayoutRtl) {
- start = 0;
- end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
+ TextView label = (TextView) mDayNamesHeader.getChildAt(0);
+ if (mShowWeekNumber) {
+ label.setVisibility(View.VISIBLE);
} else {
- start = mShowWeekNumber ? mWidth / mNumCells : 0;
- end = mWidth;
+ label.setVisibility(View.GONE);
}
-
- if (x < start || x > end) {
- outCalendar.clear();
- return false;
+ for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) {
+ label = (TextView) mDayNamesHeader.getChildAt(i);
+ if (mWeekDayTextAppearanceResId > -1) {
+ label.setTextAppearance(mContext, mWeekDayTextAppearanceResId);
+ }
+ if (i < mDaysPerWeek + 1) {
+ label.setText(mDayLabels[i - 1]);
+ label.setVisibility(View.VISIBLE);
+ } else {
+ label.setVisibility(View.GONE);
+ }
}
+ mDayNamesHeader.invalidate();
+ }
+
+ /**
+ * Sets all the required fields for the list view.
+ */
+ private void setUpListView() {
+ // Configure the listview
+ mListView.setDivider(null);
+ mListView.setItemsCanFocus(true);
+ mListView.setVerticalScrollBarEnabled(false);
+ mListView.setOnScrollListener(new OnScrollListener() {
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ LegacyCalendarViewDelegate.this.onScrollStateChanged(view, scrollState);
+ }
- // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
- int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
+ public void onScroll(
+ AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ LegacyCalendarViewDelegate.this.onScroll(view, firstVisibleItem,
+ visibleItemCount, totalItemCount);
+ }
+ });
+ // Make the scrolling behavior nicer
+ mListView.setFriction(mFriction);
+ mListView.setVelocityScale(mVelocityScale);
+ }
- if (isLayoutRtl) {
- dayPosition = mDaysPerWeek - 1 - dayPosition;
+ /**
+ * This moves to the specified time in the view. If the time is not already
+ * in range it will move the list so that the first of the month containing
+ * the time is at the top of the view. If the new time is already in view
+ * the list will not be scrolled unless forceScroll is true. This time may
+ * optionally be highlighted as selected as well.
+ *
+ * @param date The time to move to.
+ * @param animate Whether to scroll to the given time or just redraw at the
+ * new location.
+ * @param setSelected Whether to set the given time as selected.
+ * @param forceScroll Whether to recenter even if the time is already
+ * visible.
+ *
+ * @throws IllegalArgumentException of the provided date is before the
+ * range start of after the range end.
+ */
+ private void goTo(Calendar date, boolean animate, boolean setSelected,
+ boolean forceScroll) {
+ if (date.before(mMinDate) || date.after(mMaxDate)) {
+ throw new IllegalArgumentException("Time not between " + mMinDate.getTime()
+ + " and " + mMaxDate.getTime());
+ }
+ // Find the first and last entirely visible weeks
+ int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
+ View firstChild = mListView.getChildAt(0);
+ if (firstChild != null && firstChild.getTop() < 0) {
+ firstFullyVisiblePosition++;
+ }
+ int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1;
+ if (firstChild != null && firstChild.getTop() > mBottomBuffer) {
+ lastFullyVisiblePosition--;
+ }
+ if (setSelected) {
+ mAdapter.setSelectedDay(date);
}
+ // Get the week we're going to
+ int position = getWeeksSinceMinDate(date);
- outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
- outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
+ // Check if the selected day is now outside of our visible range
+ // and if so scroll to the month that contains it
+ if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition
+ || forceScroll) {
+ mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis());
+ mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1);
- return true;
- }
+ setMonthDisplayed(mFirstDayOfMonth);
- @Override
- protected void onDraw(Canvas canvas) {
- drawBackground(canvas);
- drawWeekNumbersAndDates(canvas);
- drawWeekSeparators(canvas);
- drawSelectedDateVerticalBars(canvas);
+ // the earliest time we can scroll to is the min date
+ if (mFirstDayOfMonth.before(mMinDate)) {
+ position = 0;
+ } else {
+ position = getWeeksSinceMinDate(mFirstDayOfMonth);
+ }
+
+ mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
+ if (animate) {
+ mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset,
+ GOTO_SCROLL_DURATION);
+ } else {
+ mListView.setSelectionFromTop(position, mListScrollTopOffset);
+ // Perform any after scroll operations that are needed
+ onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE);
+ }
+ } else if (setSelected) {
+ // Otherwise just set the selection
+ setMonthDisplayed(date);
+ }
}
/**
- * This draws the selection highlight if a day is selected in this week.
+ * Parses the given <code>date</code> and in case of success sets
+ * the result to the <code>outDate</code>.
*
- * @param canvas The canvas to draw on
+ * @return True if the date was parsed.
*/
- private void drawBackground(Canvas canvas) {
- if (!mHasSelectedDay) {
- return;
+ private boolean parseDate(String date, Calendar outDate) {
+ try {
+ outDate.setTime(mDateFormat.parse(date));
+ return true;
+ } catch (ParseException e) {
+ Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
+ return false;
}
- mDrawPaint.setColor(mSelectedWeekBackgroundColor);
+ }
- mTempRect.top = mWeekSeperatorLineWidth;
- mTempRect.bottom = mHeight;
+ /**
+ * Called when a <code>view</code> transitions to a new <code>scrollState
+ * </code>.
+ */
+ private void onScrollStateChanged(AbsListView view, int scrollState) {
+ mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
+ }
- final boolean isLayoutRtl = isLayoutRtl();
+ /**
+ * Updates the title and selected month if the <code>view</code> has moved to a new
+ * month.
+ */
+ private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ WeekView child = (WeekView) view.getChildAt(0);
+ if (child == null) {
+ return;
+ }
- if (isLayoutRtl) {
- mTempRect.left = 0;
- mTempRect.right = mSelectedLeft - 2;
+ // Figure out where we are
+ long currScroll =
+ view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
+
+ // If we have moved since our last call update the direction
+ if (currScroll < mPreviousScrollPosition) {
+ mIsScrollingUp = true;
+ } else if (currScroll > mPreviousScrollPosition) {
+ mIsScrollingUp = false;
} else {
- mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
- mTempRect.right = mSelectedLeft - 2;
+ return;
}
- canvas.drawRect(mTempRect, mDrawPaint);
- if (isLayoutRtl) {
- mTempRect.left = mSelectedRight + 3;
- mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
- } else {
- mTempRect.left = mSelectedRight + 3;
- mTempRect.right = mWidth;
+ // Use some hysteresis for checking which month to highlight. This
+ // causes the month to transition when two full weeks of a month are
+ // visible when scrolling up, and when the first day in a month reaches
+ // the top of the screen when scrolling down.
+ int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0;
+ if (mIsScrollingUp) {
+ child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset);
+ } else if (offset != 0) {
+ child = (WeekView) view.getChildAt(offset);
}
- canvas.drawRect(mTempRect, mDrawPaint);
- }
- /**
- * Draws the week and month day numbers for this week.
- *
- * @param canvas The canvas to draw on
- */
- private void drawWeekNumbersAndDates(Canvas canvas) {
- final float textHeight = mDrawPaint.getTextSize();
- final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
- final int nDays = mNumCells;
- final int divisor = 2 * nDays;
-
- mDrawPaint.setTextAlign(Align.CENTER);
- mDrawPaint.setTextSize(mDateTextSize);
-
- int i = 0;
-
- if (isLayoutRtl()) {
- for (; i < nDays - 1; i++) {
- mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
- : mUnfocusedMonthDateColor);
- int x = (2 * i + 1) * mWidth / divisor;
- canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint);
- }
- if (mShowWeekNumber) {
- mDrawPaint.setColor(mWeekNumberColor);
- int x = mWidth - mWidth / divisor;
- canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
+ if (child != null) {
+ // Find out which month we're moving into
+ int month;
+ if (mIsScrollingUp) {
+ month = child.getMonthOfFirstWeekDay();
+ } else {
+ month = child.getMonthOfLastWeekDay();
}
- } else {
- if (mShowWeekNumber) {
- mDrawPaint.setColor(mWeekNumberColor);
- int x = mWidth / divisor;
- canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
- i++;
+
+ // And how it relates to our current highlighted month
+ int monthDiff;
+ if (mCurrentMonthDisplayed == 11 && month == 0) {
+ monthDiff = 1;
+ } else if (mCurrentMonthDisplayed == 0 && month == 11) {
+ monthDiff = -1;
+ } else {
+ monthDiff = month - mCurrentMonthDisplayed;
}
- for (; i < nDays; i++) {
- mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
- : mUnfocusedMonthDateColor);
- int x = (2 * i + 1) * mWidth / divisor;
- canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
+
+ // Only switch months if we're scrolling away from the currently
+ // selected month
+ if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) {
+ Calendar firstDay = child.getFirstDay();
+ if (mIsScrollingUp) {
+ firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK);
+ } else {
+ firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK);
+ }
+ setMonthDisplayed(firstDay);
}
}
+ mPreviousScrollPosition = currScroll;
+ mPreviousScrollState = mCurrentScrollState;
}
/**
- * Draws a horizontal line for separating the weeks.
+ * Sets the month displayed at the top of this view based on time. Override
+ * to add custom events when the title is changed.
*
- * @param canvas The canvas to draw on.
+ * @param calendar A day in the new focus month.
*/
- private void drawWeekSeparators(Canvas canvas) {
- // If it is the topmost fully visible child do not draw separator line
- int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
- if (mListView.getChildAt(0).getTop() < 0) {
- firstFullyVisiblePosition++;
+ private void setMonthDisplayed(Calendar calendar) {
+ mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
+ mAdapter.setFocusMonth(mCurrentMonthDisplayed);
+ final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
+ | DateUtils.FORMAT_SHOW_YEAR;
+ final long millis = calendar.getTimeInMillis();
+ String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
+ mMonthName.setText(newMonthName);
+ mMonthName.invalidate();
+ }
+
+ /**
+ * @return Returns the number of weeks between the current <code>date</code>
+ * and the <code>mMinDate</code>.
+ */
+ private int getWeeksSinceMinDate(Calendar date) {
+ if (date.before(mMinDate)) {
+ throw new IllegalArgumentException("fromDate: " + mMinDate.getTime()
+ + " does not precede toDate: " + date.getTime());
}
- if (firstFullyVisiblePosition == mWeek) {
- return;
+ long endTimeMillis = date.getTimeInMillis()
+ + date.getTimeZone().getOffset(date.getTimeInMillis());
+ long startTimeMillis = mMinDate.getTimeInMillis()
+ + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis());
+ long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek)
+ * MILLIS_IN_DAY;
+ return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK);
+ }
+
+ /**
+ * Command responsible for acting upon scroll state changes.
+ */
+ private class ScrollStateRunnable implements Runnable {
+ private AbsListView mView;
+
+ private int mNewState;
+
+ /**
+ * Sets up the runnable with a short delay in case the scroll state
+ * immediately changes again.
+ *
+ * @param view The list view that changed state
+ * @param scrollState The new state it changed to
+ */
+ public void doScrollStateChange(AbsListView view, int scrollState) {
+ mView = view;
+ mNewState = scrollState;
+ mDelegator.removeCallbacks(this);
+ mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY);
}
- mDrawPaint.setColor(mWeekSeparatorLineColor);
- mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth);
- float startX;
- float stopX;
- if (isLayoutRtl()) {
- startX = 0;
- stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
- } else {
- startX = mShowWeekNumber ? mWidth / mNumCells : 0;
- stopX = mWidth;
+
+ public void run() {
+ mCurrentScrollState = mNewState;
+ // Fix the position after a scroll or a fling ends
+ if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
+ && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
+ View child = mView.getChildAt(0);
+ if (child == null) {
+ // The view is no longer visible, just return
+ return;
+ }
+ int dist = child.getBottom() - mListScrollTopOffset;
+ if (dist > mListScrollTopOffset) {
+ if (mIsScrollingUp) {
+ mView.smoothScrollBy(dist - child.getHeight(),
+ ADJUSTMENT_SCROLL_DURATION);
+ } else {
+ mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION);
+ }
+ }
+ }
+ mPreviousScrollState = mNewState;
}
- canvas.drawLine(startX, 0, stopX, 0, mDrawPaint);
}
/**
- * Draws the selected date bars if this week has a selected day.
- *
- * @param canvas The canvas to draw on
+ * <p>
+ * This is a specialized adapter for creating a list of weeks with
+ * selectable days. It can be configured to display the week number, start
+ * the week on a given day, show a reduced number of days, or display an
+ * arbitrary number of weeks at a time.
+ * </p>
*/
- private void drawSelectedDateVerticalBars(Canvas canvas) {
- if (!mHasSelectedDay) {
- return;
+ private class WeeksAdapter extends BaseAdapter implements OnTouchListener {
+
+ private int mSelectedWeek;
+
+ private GestureDetector mGestureDetector;
+
+ private int mFocusedMonth;
+
+ private final Calendar mSelectedDate = Calendar.getInstance();
+
+ private int mTotalWeekCount;
+
+ public WeeksAdapter(Context context) {
+ mContext = context;
+ mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
+ init();
}
- mSelectedDateVerticalBar.setBounds(mSelectedLeft - mSelectedDateVerticalBarWidth / 2,
- mWeekSeperatorLineWidth,
- mSelectedLeft + mSelectedDateVerticalBarWidth / 2, mHeight);
- mSelectedDateVerticalBar.draw(canvas);
- mSelectedDateVerticalBar.setBounds(mSelectedRight - mSelectedDateVerticalBarWidth / 2,
- mWeekSeperatorLineWidth,
- mSelectedRight + mSelectedDateVerticalBarWidth / 2, mHeight);
- mSelectedDateVerticalBar.draw(canvas);
- }
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- mWidth = w;
- updateSelectionPositions();
+ /**
+ * Set up the gesture detector and selected time
+ */
+ private void init() {
+ mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
+ mTotalWeekCount = getWeeksSinceMinDate(mMaxDate);
+ if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek
+ || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) {
+ mTotalWeekCount++;
+ }
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Updates the selected day and related parameters.
+ *
+ * @param selectedDay The time to highlight
+ */
+ public void setSelectedDay(Calendar selectedDay) {
+ if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR)
+ && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) {
+ return;
+ }
+ mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis());
+ mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
+ mFocusedMonth = mSelectedDate.get(Calendar.MONTH);
+ notifyDataSetChanged();
+ }
+
+ /**
+ * @return The selected day of month.
+ */
+ public Calendar getSelectedDay() {
+ return mSelectedDate;
+ }
+
+ @Override
+ public int getCount() {
+ return mTotalWeekCount;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ WeekView weekView = null;
+ if (convertView != null) {
+ weekView = (WeekView) convertView;
+ } else {
+ weekView = new WeekView(mContext);
+ android.widget.AbsListView.LayoutParams params =
+ new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ weekView.setLayoutParams(params);
+ weekView.setClickable(true);
+ weekView.setOnTouchListener(this);
+ }
+
+ int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
+ Calendar.DAY_OF_WEEK) : -1;
+ weekView.init(position, selectedWeekDay, mFocusedMonth);
+
+ return weekView;
+ }
+
+ /**
+ * Changes which month is in focus and updates the view.
+ *
+ * @param month The month to show as in focus [0-11]
+ */
+ public void setFocusMonth(int month) {
+ if (mFocusedMonth == month) {
+ return;
+ }
+ mFocusedMonth = month;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) {
+ WeekView weekView = (WeekView) v;
+ // if we cannot find a day for the given location we are done
+ if (!weekView.getDayFromLocation(event.getX(), mTempDate)) {
+ return true;
+ }
+ // it is possible that the touched day is outside the valid range
+ // we draw whole weeks but range end can fall not on the week end
+ if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
+ return true;
+ }
+ onDateTapped(mTempDate);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Maintains the same hour/min/sec but moves the day to the tapped day.
+ *
+ * @param day The day that was tapped
+ */
+ private void onDateTapped(Calendar day) {
+ setSelectedDay(day);
+ setMonthDisplayed(day);
+ }
+
+ /**
+ * This is here so we can identify single tap events and set the
+ * selected day correctly
+ */
+ class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return true;
+ }
+ }
}
/**
- * This calculates the positions for the selected day lines.
+ * <p>
+ * This is a dynamic view for drawing a single week. It can be configured to
+ * display the week number, start the week on a given day, or show a reduced
+ * number of days. It is intended for use as a single view within a
+ * ListView. See {@link WeeksAdapter} for usage.
+ * </p>
*/
- private void updateSelectionPositions() {
- if (mHasSelectedDay) {
+ private class WeekView extends View {
+
+ private final Rect mTempRect = new Rect();
+
+ private final Paint mDrawPaint = new Paint();
+
+ private final Paint mMonthNumDrawPaint = new Paint();
+
+ // Cache the number strings so we don't have to recompute them each time
+ private String[] mDayNumbers;
+
+ // Quick lookup for checking which days are in the focus month
+ private boolean[] mFocusDay;
+
+ // Whether this view has a focused day.
+ private boolean mHasFocusedDay;
+
+ // Whether this view has only focused days.
+ private boolean mHasUnfocusedDay;
+
+ // The first day displayed by this item
+ private Calendar mFirstDay;
+
+ // The month of the first day in this week
+ private int mMonthOfFirstWeekDay = -1;
+
+ // The month of the last day in this week
+ private int mLastWeekDayMonth = -1;
+
+ // The position of this week, equivalent to weeks since the week of Jan
+ // 1st, 1900
+ private int mWeek = -1;
+
+ // Quick reference to the width of this view, matches parent
+ private int mWidth;
+
+ // The height this view should draw at in pixels, set by height param
+ private int mHeight;
+
+ // If this view contains the selected day
+ private boolean mHasSelectedDay = false;
+
+ // Which day is selected [0-6] or -1 if no day is selected
+ private int mSelectedDay = -1;
+
+ // The number of days + a spot for week number if it is displayed
+ private int mNumCells;
+
+ // The left edge of the selected day
+ private int mSelectedLeft = -1;
+
+ // The right edge of the selected day
+ private int mSelectedRight = -1;
+
+ public WeekView(Context context) {
+ super(context);
+
+ // Sets up any standard paints that will be used
+ initilaizePaints();
+ }
+
+ /**
+ * Initializes this week view.
+ *
+ * @param weekNumber The number of the week this view represents. The
+ * week number is a zero based index of the weeks since
+ * {@link CalendarView#getMinDate()}.
+ * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no
+ * selected day.
+ * @param focusedMonth The month that is currently in focus i.e.
+ * highlighted.
+ */
+ public void init(int weekNumber, int selectedWeekDay, int focusedMonth) {
+ mSelectedDay = selectedWeekDay;
+ mHasSelectedDay = mSelectedDay != -1;
+ mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek;
+ mWeek = weekNumber;
+ mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
+
+ mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek);
+ mTempDate.setFirstDayOfWeek(mFirstDayOfWeek);
+
+ // Allocate space for caching the day numbers and focus values
+ mDayNumbers = new String[mNumCells];
+ mFocusDay = new boolean[mNumCells];
+
+ // If we're showing the week number calculate it based on Monday
+ int i = 0;
+ if (mShowWeekNumber) {
+ mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
+ mTempDate.get(Calendar.WEEK_OF_YEAR));
+ i++;
+ }
+
+ // Now adjust our starting day based on the start day of the week
+ int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK);
+ mTempDate.add(Calendar.DAY_OF_MONTH, diff);
+
+ mFirstDay = (Calendar) mTempDate.clone();
+ mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH);
+
+ mHasUnfocusedDay = true;
+ for (; i < mNumCells; i++) {
+ final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth);
+ mFocusDay[i] = isFocusedDay;
+ mHasFocusedDay |= isFocusedDay;
+ mHasUnfocusedDay &= !isFocusedDay;
+ // do not draw dates outside the valid range to avoid user confusion
+ if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
+ mDayNumbers[i] = "";
+ } else {
+ mDayNumbers[i] = String.format(Locale.getDefault(), "%d",
+ mTempDate.get(Calendar.DAY_OF_MONTH));
+ }
+ mTempDate.add(Calendar.DAY_OF_MONTH, 1);
+ }
+ // We do one extra add at the end of the loop, if that pushed us to
+ // new month undo it
+ if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) {
+ mTempDate.add(Calendar.DAY_OF_MONTH, -1);
+ }
+ mLastWeekDayMonth = mTempDate.get(Calendar.MONTH);
+
+ updateSelectionPositions();
+ }
+
+ /**
+ * Initialize the paint instances.
+ */
+ private void initilaizePaints() {
+ mDrawPaint.setFakeBoldText(false);
+ mDrawPaint.setAntiAlias(true);
+ mDrawPaint.setStyle(Style.FILL);
+
+ mMonthNumDrawPaint.setFakeBoldText(true);
+ mMonthNumDrawPaint.setAntiAlias(true);
+ mMonthNumDrawPaint.setStyle(Style.FILL);
+ mMonthNumDrawPaint.setTextAlign(Align.CENTER);
+ mMonthNumDrawPaint.setTextSize(mDateTextSize);
+ }
+
+ /**
+ * Returns the month of the first day in this week.
+ *
+ * @return The month the first day of this view is in.
+ */
+ public int getMonthOfFirstWeekDay() {
+ return mMonthOfFirstWeekDay;
+ }
+
+ /**
+ * Returns the month of the last day in this week
+ *
+ * @return The month the last day of this view is in
+ */
+ public int getMonthOfLastWeekDay() {
+ return mLastWeekDayMonth;
+ }
+
+ /**
+ * Returns the first day in this view.
+ *
+ * @return The first day in the view.
+ */
+ public Calendar getFirstDay() {
+ return mFirstDay;
+ }
+
+ /**
+ * Calculates the day that the given x position is in, accounting for
+ * week number.
+ *
+ * @param x The x position of the touch event.
+ * @return True if a day was found for the given location.
+ */
+ public boolean getDayFromLocation(float x, Calendar outCalendar) {
final boolean isLayoutRtl = isLayoutRtl();
- int selectedPosition = mSelectedDay - mFirstDayOfWeek;
- if (selectedPosition < 0) {
- selectedPosition += 7;
+
+ int start;
+ int end;
+
+ if (isLayoutRtl) {
+ start = 0;
+ end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
+ } else {
+ start = mShowWeekNumber ? mWidth / mNumCells : 0;
+ end = mWidth;
+ }
+
+ if (x < start || x > end) {
+ outCalendar.clear();
+ return false;
+ }
+
+ // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
+ int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
+
+ if (isLayoutRtl) {
+ dayPosition = mDaysPerWeek - 1 - dayPosition;
+ }
+
+ outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
+ outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
+
+ return true;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ drawBackground(canvas);
+ drawWeekNumbersAndDates(canvas);
+ drawWeekSeparators(canvas);
+ drawSelectedDateVerticalBars(canvas);
+ }
+
+ /**
+ * This draws the selection highlight if a day is selected in this week.
+ *
+ * @param canvas The canvas to draw on
+ */
+ private void drawBackground(Canvas canvas) {
+ if (!mHasSelectedDay) {
+ return;
}
- if (mShowWeekNumber && !isLayoutRtl) {
- selectedPosition++;
+ mDrawPaint.setColor(mSelectedWeekBackgroundColor);
+
+ mTempRect.top = mWeekSeperatorLineWidth;
+ mTempRect.bottom = mHeight;
+
+ final boolean isLayoutRtl = isLayoutRtl();
+
+ if (isLayoutRtl) {
+ mTempRect.left = 0;
+ mTempRect.right = mSelectedLeft - 2;
+ } else {
+ mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
+ mTempRect.right = mSelectedLeft - 2;
}
+ canvas.drawRect(mTempRect, mDrawPaint);
+
if (isLayoutRtl) {
- mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
+ mTempRect.left = mSelectedRight + 3;
+ mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
+ } else {
+ mTempRect.left = mSelectedRight + 3;
+ mTempRect.right = mWidth;
+ }
+ canvas.drawRect(mTempRect, mDrawPaint);
+ }
+ /**
+ * Draws the week and month day numbers for this week.
+ *
+ * @param canvas The canvas to draw on
+ */
+ private void drawWeekNumbersAndDates(Canvas canvas) {
+ final float textHeight = mDrawPaint.getTextSize();
+ final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
+ final int nDays = mNumCells;
+ final int divisor = 2 * nDays;
+
+ mDrawPaint.setTextAlign(Align.CENTER);
+ mDrawPaint.setTextSize(mDateTextSize);
+
+ int i = 0;
+
+ if (isLayoutRtl()) {
+ for (; i < nDays - 1; i++) {
+ mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
+ : mUnfocusedMonthDateColor);
+ int x = (2 * i + 1) * mWidth / divisor;
+ canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint);
+ }
+ if (mShowWeekNumber) {
+ mDrawPaint.setColor(mWeekNumberColor);
+ int x = mWidth - mWidth / divisor;
+ canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
+ }
} else {
- mSelectedLeft = selectedPosition * mWidth / mNumCells;
+ if (mShowWeekNumber) {
+ mDrawPaint.setColor(mWeekNumberColor);
+ int x = mWidth / divisor;
+ canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
+ i++;
+ }
+ for (; i < nDays; i++) {
+ mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
+ : mUnfocusedMonthDateColor);
+ int x = (2 * i + 1) * mWidth / divisor;
+ canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
+ }
}
- mSelectedRight = mSelectedLeft + mWidth / mNumCells;
}
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView
- .getPaddingBottom()) / mShownWeekCount;
- setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
+ /**
+ * Draws a horizontal line for separating the weeks.
+ *
+ * @param canvas The canvas to draw on.
+ */
+ private void drawWeekSeparators(Canvas canvas) {
+ // If it is the topmost fully visible child do not draw separator line
+ int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
+ if (mListView.getChildAt(0).getTop() < 0) {
+ firstFullyVisiblePosition++;
+ }
+ if (firstFullyVisiblePosition == mWeek) {
+ return;
+ }
+ mDrawPaint.setColor(mWeekSeparatorLineColor);
+ mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth);
+ float startX;
+ float stopX;
+ if (isLayoutRtl()) {
+ startX = 0;
+ stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
+ } else {
+ startX = mShowWeekNumber ? mWidth / mNumCells : 0;
+ stopX = mWidth;
+ }
+ canvas.drawLine(startX, 0, stopX, 0, mDrawPaint);
+ }
+
+ /**
+ * Draws the selected date bars if this week has a selected day.
+ *
+ * @param canvas The canvas to draw on
+ */
+ private void drawSelectedDateVerticalBars(Canvas canvas) {
+ if (!mHasSelectedDay) {
+ return;
+ }
+ mSelectedDateVerticalBar.setBounds(
+ mSelectedLeft - mSelectedDateVerticalBarWidth / 2,
+ mWeekSeperatorLineWidth,
+ mSelectedLeft + mSelectedDateVerticalBarWidth / 2,
+ mHeight);
+ mSelectedDateVerticalBar.draw(canvas);
+ mSelectedDateVerticalBar.setBounds(
+ mSelectedRight - mSelectedDateVerticalBarWidth / 2,
+ mWeekSeperatorLineWidth,
+ mSelectedRight + mSelectedDateVerticalBarWidth / 2,
+ mHeight);
+ mSelectedDateVerticalBar.draw(canvas);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWidth = w;
+ updateSelectionPositions();
+ }
+
+ /**
+ * This calculates the positions for the selected day lines.
+ */
+ private void updateSelectionPositions() {
+ if (mHasSelectedDay) {
+ final boolean isLayoutRtl = isLayoutRtl();
+ int selectedPosition = mSelectedDay - mFirstDayOfWeek;
+ if (selectedPosition < 0) {
+ selectedPosition += 7;
+ }
+ if (mShowWeekNumber && !isLayoutRtl) {
+ selectedPosition++;
+ }
+ if (isLayoutRtl) {
+ mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
+
+ } else {
+ mSelectedLeft = selectedPosition * mWidth / mNumCells;
+ }
+ mSelectedRight = mSelectedLeft + mWidth / mNumCells;
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView
+ .getPaddingBottom()) / mShownWeekCount;
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
+ }
}
+
}
+
}
diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java
index f1804f8..71438c9 100644
--- a/core/java/android/widget/CheckBox.java
+++ b/core/java/android/widget/CheckBox.java
@@ -64,8 +64,12 @@ public class CheckBox extends CompoundButton {
this(context, attrs, com.android.internal.R.attr.checkboxStyle);
}
- public CheckBox(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public CheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public CheckBox(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index 5c10a77..1533510 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -58,11 +58,15 @@ public class CheckedTextView extends TextView implements Checkable {
this(context, attrs, R.attr.checkedTextViewStyle);
}
- public CheckedTextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public CheckedTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public CheckedTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.CheckedTextView, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.CheckedTextView, defStyleAttr, defStyleRes);
Drawable d = a.getDrawable(R.styleable.CheckedTextView_checkMark);
if (d != null) {
@@ -119,7 +123,7 @@ public class CheckedTextView extends TextView implements Checkable {
Drawable d = null;
if (mCheckMarkResource != 0) {
- d = getResources().getDrawable(mCheckMarkResource);
+ d = getContext().getDrawable(mCheckMarkResource);
}
setCheckMarkDrawable(d);
}
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index b7a126e..f94789d 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -18,14 +18,12 @@ package android.widget;
import android.content.Context;
import android.content.res.TypedArray;
-import android.graphics.Canvas;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.Slog;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
@@ -96,12 +94,15 @@ public class Chronometer extends TextView {
* Initialize with standard view layout information and style.
* Sets the base to the current time.
*/
- public Chronometer(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public Chronometer(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public Chronometer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(
- attrs,
- com.android.internal.R.styleable.Chronometer, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.Chronometer, defStyleAttr, defStyleRes);
setFormat(a.getString(com.android.internal.R.styleable.Chronometer_format));
a.recycle();
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index abddc90..9e17cca 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);
}
@@ -258,15 +261,13 @@ public abstract class CompoundButton extends Button implements Checkable {
@Override
protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
final Drawable buttonDrawable = mButtonDrawable;
if (buttonDrawable != null) {
final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
final int drawableHeight = buttonDrawable.getIntrinsicHeight();
final int drawableWidth = buttonDrawable.getIntrinsicWidth();
- int top = 0;
+ final int top;
switch (verticalGravity) {
case Gravity.BOTTOM:
top = getHeight() - drawableHeight;
@@ -274,12 +275,24 @@ public abstract class CompoundButton extends Button implements Checkable {
case Gravity.CENTER_VERTICAL:
top = (getHeight() - drawableHeight) / 2;
break;
+ default:
+ top = 0;
}
- int bottom = top + drawableHeight;
- int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;
- int right = isLayoutRtl() ? getWidth() : drawableWidth;
+ final int bottom = top + drawableHeight;
+ final int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;
+ final int right = isLayoutRtl() ? getWidth() : drawableWidth;
buttonDrawable.setBounds(left, top, right, bottom);
+
+ final Drawable background = getBackground();
+ if (background != null && background.supportsHotspots()) {
+ background.setHotspotBounds(left, top, right, bottom);
+ }
+ }
+
+ super.onDraw(canvas);
+
+ if (buttonDrawable != null) {
buttonDrawable.draw(canvas);
}
}
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..10ec105 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;
@@ -1556,7 +1565,7 @@ public class ListPopupWindow {
// Ensure that keyboard focus starts from the last touched position.
setSelectedPositionInt(position);
- positionSelector(position, child);
+ positionSelectorLikeFocus(position, child);
// Refresh the drawable state to reflect the new pressed state,
// which will also update the selector state.
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 78237c3..eeb8015 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
@@ -2539,7 +2564,7 @@ public class ListView extends AbsListView {
if (needToRedraw) {
if (selectedView != null) {
- positionSelector(selectedPos, selectedView);
+ positionSelectorLikeFocus(selectedPos, selectedView);
mSelectedTop = selectedView.getTop();
}
if (!awakenScrollBars()) {
@@ -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..0c31496 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);
}
@@ -251,6 +256,16 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
}
}
+ /**
+ * Assigns the drawable that is to be drawn on top of the assigned contact photo.
+ *
+ * @param overlay Drawable to be drawn over the assigned contact photo. Must have a non-zero
+ * instrinsic width and height.
+ */
+ public void setOverlay(Drawable overlay) {
+ mOverlay = overlay;
+ }
+
private void onContactUriChanged() {
setEnabled(isAssigned());
}
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..3e46f68 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -138,6 +138,12 @@ public class ScrollView extends FrameLayout {
private int mActivePointerId = INVALID_POINTER;
/**
+ * Used during scrolling to retrieve the new offset within the window.
+ */
+ private final int[] mScrollOffset = new int[2];
+ private final int[] mScrollConsumed = new int[2];
+
+ /**
* The StrictMode "critical time span" objects to catch animation
* stutters. Non-null when a time-sensitive animation is
* in-flight. Must call finish() on them when done animating.
@@ -162,12 +168,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));
@@ -501,7 +511,7 @@ public class ScrollView extends FrameLayout {
final int y = (int) ev.getY(pointerIndex);
final int yDiff = Math.abs(y - mLastMotionY);
- if (yDiff > mTouchSlop) {
+ if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
mIsBeingDragged = true;
mLastMotionY = y;
initVelocityTrackerIfNotExists();
@@ -543,6 +553,7 @@ public class ScrollView extends FrameLayout {
if (mIsBeingDragged && mScrollStrictSpan == null) {
mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
}
+ startNestedScroll(SCROLL_AXIS_VERTICAL);
break;
}
@@ -555,6 +566,7 @@ public class ScrollView extends FrameLayout {
if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
postInvalidateOnAnimation();
}
+ stopNestedScroll();
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
@@ -602,6 +614,7 @@ public class ScrollView extends FrameLayout {
// Remember where the motion event started
mLastMotionY = (int) ev.getY();
mActivePointerId = ev.getPointerId(0);
+ startNestedScroll(SCROLL_AXIS_VERTICAL);
break;
}
case MotionEvent.ACTION_MOVE:
@@ -613,6 +626,9 @@ public class ScrollView extends FrameLayout {
final int y = (int) ev.getY(activePointerIndex);
int deltaY = mLastMotionY - y;
+ if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
+ deltaY -= mScrollConsumed[1] + mScrollOffset[1];
+ }
if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
@@ -629,22 +645,25 @@ public class ScrollView extends FrameLayout {
// Scroll to follow the motion event
mLastMotionY = y;
- final int oldX = mScrollX;
final int oldY = mScrollY;
final int range = getScrollRange();
final int overscrollMode = getOverScrollMode();
- final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
+ boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
// Calling overScrollBy will call onOverScrolled, which
// calls onScrollChanged if applicable.
- if (overScrollBy(0, deltaY, 0, mScrollY,
- 0, range, 0, mOverscrollDistance, true)) {
+ if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)
+ && !hasNestedScrollingParent()) {
// Break our velocity if we hit a scroll barrier.
mVelocityTracker.clear();
}
- if (canOverscroll) {
+ final int scrolledDeltaY = mScrollY - oldY;
+ final int unconsumedY = deltaY - scrolledDeltaY;
+ if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) {
+ mLastMotionY -= mScrollOffset[1];
+ } else if (canOverscroll) {
final int pulledToY = oldY + deltaY;
if (pulledToY < 0) {
mEdgeGlowTop.onPull((float) deltaY / getHeight());
@@ -670,15 +689,11 @@ public class ScrollView extends FrameLayout {
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
- if (getChildCount() > 0) {
- if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
- fling(-initialVelocity);
- } else {
- if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
- getScrollRange())) {
- postInvalidateOnAnimation();
- }
- }
+ if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
+ flingWithNestedDispatch(-initialVelocity);
+ } else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
+ getScrollRange())) {
+ postInvalidateOnAnimation();
}
mActivePointerId = INVALID_POINTER;
@@ -1549,6 +1564,15 @@ public class ScrollView extends FrameLayout {
}
}
+ private void flingWithNestedDispatch(int velocityY) {
+ final boolean canFling = (mScrollY > 0 || velocityY > 0) &&
+ (mScrollY < getScrollRange() || velocityY < 0);
+ dispatchNestedFling(0, velocityY, canFling);
+ if (canFling) {
+ fling(velocityY);
+ }
+ }
+
private void endDrag() {
mIsBeingDragged = false;
@@ -1599,6 +1623,47 @@ public class ScrollView extends FrameLayout {
}
@Override
+ public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+ return (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0;
+ }
+
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int axes) {
+ super.onNestedScrollAccepted(child, target, axes);
+ startNestedScroll(SCROLL_AXIS_VERTICAL);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void onStopNestedScroll(View target) {
+ super.onStopNestedScroll(target);
+ }
+
+ @Override
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed) {
+ final int oldScrollY = mScrollY;
+ scrollBy(0, dyUnconsumed);
+ final int myConsumed = mScrollY - oldScrollY;
+ final int myUnconsumed = dyUnconsumed - myConsumed;
+ dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+ if (!consumed) {
+ flingWithNestedDispatch((int) velocityY);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mEdgeGlowTop != null) {
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..08af4de 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;
@@ -714,49 +773,59 @@ public class Switch extends CompoundButton {
@Override
protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
+ final Rect tempRect = mTempRect;
+ final Drawable trackDrawable = mTrackDrawable;
+ final Drawable thumbDrawable = mThumbDrawable;
// Draw the switch
- int switchLeft = mSwitchLeft;
- int switchTop = mSwitchTop;
- int switchRight = mSwitchRight;
- int switchBottom = mSwitchBottom;
+ final int switchLeft = mSwitchLeft;
+ final int switchTop = mSwitchTop;
+ final int switchRight = mSwitchRight;
+ final int switchBottom = mSwitchBottom;
+ trackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
+ 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;
+
+ // Relies on mTempRect, MUST be called first!
+ final int thumbPos = getThumbOffset();
+
+ thumbDrawable.getPadding(tempRect);
+ int thumbLeft = switchInnerLeft - tempRect.left + thumbPos;
+ int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + tempRect.right;
+ thumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
+
+ final Drawable background = getBackground();
+ if (background != null && background.supportsHotspots()) {
+ background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom);
+ }
- mTrackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
- mTrackDrawable.draw(canvas);
+ super.onDraw(canvas);
- canvas.save();
+ trackDrawable.draw(canvas);
- mTrackDrawable.getPadding(mTempRect);
- int switchInnerLeft = switchLeft + mTempRect.left;
- int switchInnerTop = switchTop + mTempRect.top;
- int switchInnerRight = switchRight - mTempRect.right;
- int switchInnerBottom = switchBottom - mTempRect.bottom;
+ final int saveCount = canvas.save();
canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
+ thumbDrawable.draw(canvas);
- mThumbDrawable.getPadding(mTempRect);
- final int thumbPos = (int) (mThumbPosition + 0.5f);
- int thumbLeft = switchInnerLeft - mTempRect.left + thumbPos;
- int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + mTempRect.right;
-
- mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
- mThumbDrawable.draw(canvas);
-
- // 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 +852,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 +933,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..b91111d 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();
@@ -648,6 +652,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean allCaps = false;
int shadowcolor = 0;
float dx = 0, dy = 0, r = 0;
+ boolean elegant = false;
final Resources.Theme theme = context.getTheme();
@@ -657,8 +662,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);
@@ -724,6 +729,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case com.android.internal.R.styleable.TextAppearance_shadowRadius:
r = appearance.getFloat(attr, 0);
break;
+
+ case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
+ elegant = appearance.getBoolean(attr, false);
+ break;
}
}
@@ -751,7 +760,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++) {
@@ -1061,6 +1070,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case com.android.internal.R.styleable.TextView_textAllCaps:
allCaps = a.getBoolean(attr, false);
break;
+
+ case com.android.internal.R.styleable.TextView_elegantTextHeight:
+ elegant = a.getBoolean(attr, false);
+ break;
}
}
a.recycle();
@@ -1241,6 +1254,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setHighlightColor(textColorHighlight);
}
setRawTextSize(textSize);
+ setElegantTextHeight(elegant);
if (allCaps) {
setTransformationMethod(new AllCapsTransformationMethod(getContext()));
@@ -1275,9 +1289,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 +2083,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 +2257,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);
}
/**
@@ -2465,6 +2478,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setTransformationMethod(new AllCapsTransformationMethod(getContext()));
}
+ if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_elegantTextHeight)) {
+ setElegantTextHeight(appearance.getBoolean(
+ com.android.internal.R.styleable.TextAppearance_elegantTextHeight, false));
+ }
+
appearance.recycle();
}
@@ -2612,6 +2630,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Set the TextView's elegant height metrics flag. This setting selects font
+ * variants that have not been compacted to fit Latin-based vertical
+ * metrics, and also increases top and bottom bounds to provide more space.
+ *
+ * @param elegant set the paint's elegant metrics flag.
+ */
+ public void setElegantTextHeight(boolean elegant) {
+ mTextPaint.setElegantTextHeight(elegant);
+ }
+
+ /**
* Sets the text color for all the states (normal, selected,
* focused) to be this color.
*
@@ -4382,8 +4411,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 +4755,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 +4766,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
resetResolvedDrawables();
if (mEditor != null) mEditor.onDetachedFromWindow();
+
+ super.onDetachedFromWindowInternal();
}
@Override
@@ -4811,6 +4841,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 +4860,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 +4868,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 +4876,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 +4884,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 +5836,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 +8521,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;
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index fe532b0..4726da7 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -70,6 +70,8 @@ public class AlertController {
private View mView;
+ private int mViewLayoutResId;
+
private int mViewSpacingLeft;
private int mViewSpacingTop;
@@ -100,7 +102,7 @@ public class AlertController {
private ScrollView mScrollView;
- private int mIconId = -1;
+ private int mIconId = 0;
private Drawable mIcon;
@@ -126,16 +128,20 @@ public class AlertController {
private Handler mHandler;
- View.OnClickListener mButtonHandler = new View.OnClickListener() {
+ private final View.OnClickListener mButtonHandler = new View.OnClickListener() {
+ @Override
public void onClick(View v) {
- Message m = null;
+ final Message m;
if (v == mButtonPositive && mButtonPositiveMessage != null) {
m = Message.obtain(mButtonPositiveMessage);
} else if (v == mButtonNegative && mButtonNegativeMessage != null) {
m = Message.obtain(mButtonNegativeMessage);
} else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
m = Message.obtain(mButtonNeutralMessage);
+ } else {
+ m = null;
}
+
if (m != null) {
m.sendToTarget();
}
@@ -232,11 +238,6 @@ public class AlertController {
public void installContent() {
/* We use a custom title so never request a window title */
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
-
- if (mView == null || !canTextInput(mView)) {
- mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
- WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
- }
mWindow.setContentView(mAlertDialogLayout);
setupView();
}
@@ -263,10 +264,20 @@ public class AlertController {
}
/**
+ * Set the view resource to display in the dialog.
+ */
+ public void setView(int layoutResId) {
+ mView = null;
+ mViewLayoutResId = layoutResId;
+ mViewSpacingSpecified = false;
+ }
+
+ /**
* Set the view to display in the dialog.
*/
public void setView(View view) {
mView = view;
+ mViewLayoutResId = 0;
mViewSpacingSpecified = false;
}
@@ -276,6 +287,7 @@ public class AlertController {
public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
int viewSpacingBottom) {
mView = view;
+ mViewLayoutResId = 0;
mViewSpacingSpecified = true;
mViewSpacingLeft = viewSpacingLeft;
mViewSpacingTop = viewSpacingTop;
@@ -325,25 +337,39 @@ public class AlertController {
}
/**
- * Set resId to 0 if you don't want an icon.
- * @param resId the resourceId of the drawable to use as the icon or 0
- * if you don't want an icon.
+ * Specifies the icon to display next to the alert title.
+ *
+ * @param resId the resource identifier of the drawable to use as the icon,
+ * or 0 for no icon
*/
public void setIcon(int resId) {
+ mIcon = null;
mIconId = resId;
+
if (mIconView != null) {
- if (resId > 0) {
+ if (resId != 0) {
mIconView.setImageResource(mIconId);
- } else if (resId == 0) {
+ } else {
mIconView.setVisibility(View.GONE);
}
}
}
-
+
+ /**
+ * Specifies the icon to display next to the alert title.
+ *
+ * @param icon the drawable to use as the icon or null for no icon
+ */
public void setIcon(Drawable icon) {
mIcon = icon;
- if ((mIconView != null) && (mIcon != null)) {
- mIconView.setImageDrawable(icon);
+ mIconId = 0;
+
+ if (mIconView != null) {
+ if (icon != null) {
+ mIconView.setImageDrawable(icon);
+ } else {
+ mIconView.setVisibility(View.GONE);
+ }
}
}
@@ -406,28 +432,44 @@ public class AlertController {
mWindow.setCloseOnTouchOutsideIfNotSet(true);
}
- FrameLayout customPanel = null;
+ final FrameLayout customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
+ final View customView;
if (mView != null) {
- customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
- FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
- custom.addView(mView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ customView = mView;
+ } else if (mViewLayoutResId != 0) {
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ customView = inflater.inflate(mViewLayoutResId, customPanel, false);
+ } else {
+ customView = null;
+ }
+
+ final boolean hasCustomView = customView != null;
+ if (!hasCustomView || !canTextInput(customView)) {
+ mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ }
+
+ if (hasCustomView) {
+ final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
+ custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
if (mViewSpacingSpecified) {
- custom.setPadding(mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
- mViewSpacingBottom);
+ custom.setPadding(
+ mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
}
+
if (mListView != null) {
((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
}
} else {
- mWindow.findViewById(R.id.customPanel).setVisibility(View.GONE);
+ customPanel.setVisibility(View.GONE);
}
-
- /* Only display the divider if we have a title and a
- * custom view or a message.
- */
+
+ // Only display the divider if we have a title and a custom view or a
+ // message.
if (hasTitle) {
- View divider = null;
- if (mMessage != null || mView != null || mListView != null) {
+ final View divider;
+ if (mMessage != null || customView != null || mListView != null) {
divider = mWindow.findViewById(R.id.titleDivider);
} else {
divider = mWindow.findViewById(R.id.titleDividerTop);
@@ -437,8 +479,9 @@ public class AlertController {
divider.setVisibility(View.VISIBLE);
}
}
-
- setBackground(topPanel, contentPanel, customPanel, hasButtons, a, hasTitle, buttonPanel);
+
+ setBackground(a, topPanel, contentPanel, customPanel, buttonPanel, hasTitle, hasCustomView,
+ hasButtons);
a.recycle();
}
@@ -456,28 +499,24 @@ public class AlertController {
View titleTemplate = mWindow.findViewById(R.id.title_template);
titleTemplate.setVisibility(View.GONE);
} else {
- final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
-
mIconView = (ImageView) mWindow.findViewById(R.id.icon);
+
+ final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
if (hasTextTitle) {
- /* Display the title if a title is supplied, else hide it */
+ // Display the title if a title is supplied, else hide it.
mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
-
mTitleView.setText(mTitle);
-
- /* Do this last so that if the user has supplied any
- * icons we use them instead of the default ones. If the
- * user has specified 0 then make it disappear.
- */
- if (mIconId > 0) {
+
+ // Do this last so that if the user has supplied any icons we
+ // use them instead of the default ones. If the user has
+ // specified 0 then make it disappear.
+ if (mIconId != 0) {
mIconView.setImageResource(mIconId);
} else if (mIcon != null) {
mIconView.setImageDrawable(mIcon);
- } else if (mIconId == 0) {
-
- /* Apply the padding from the icon to ensure the
- * title is aligned correctly.
- */
+ } else {
+ // Apply the padding from the icon to ensure the title is
+ // aligned correctly.
mTitleView.setPadding(mIconView.getPaddingLeft(),
mIconView.getPaddingTop(),
mIconView.getPaddingRight(),
@@ -485,9 +524,8 @@ public class AlertController {
mIconView.setVisibility(View.GONE);
}
} else {
-
// Hide the title template
- View titleTemplate = mWindow.findViewById(R.id.title_template);
+ final View titleTemplate = mWindow.findViewById(R.id.title_template);
titleTemplate.setVisibility(View.GONE);
mIconView.setVisibility(View.GONE);
topPanel.setVisibility(View.GONE);
@@ -596,76 +634,64 @@ public class AlertController {
}
}
- private void setBackground(LinearLayout topPanel, LinearLayout contentPanel,
- View customPanel, boolean hasButtons, TypedArray a, boolean hasTitle,
- View buttonPanel) {
-
- /* Get all the different background required */
- int fullDark = a.getResourceId(
- R.styleable.AlertDialog_fullDark, R.drawable.popup_full_dark);
- int topDark = a.getResourceId(
- R.styleable.AlertDialog_topDark, R.drawable.popup_top_dark);
- int centerDark = a.getResourceId(
- R.styleable.AlertDialog_centerDark, R.drawable.popup_center_dark);
- int bottomDark = a.getResourceId(
- R.styleable.AlertDialog_bottomDark, R.drawable.popup_bottom_dark);
- int fullBright = a.getResourceId(
- R.styleable.AlertDialog_fullBright, R.drawable.popup_full_bright);
- int topBright = a.getResourceId(
+ private void setBackground(TypedArray a, View topPanel, View contentPanel, View customPanel,
+ View buttonPanel, boolean hasTitle, boolean hasCustomView, boolean hasButtons) {
+ final int topBright = a.getResourceId(
R.styleable.AlertDialog_topBright, R.drawable.popup_top_bright);
- int centerBright = a.getResourceId(
+ final int topDark = a.getResourceId(
+ R.styleable.AlertDialog_topDark, R.drawable.popup_top_dark);
+ final int centerBright = a.getResourceId(
R.styleable.AlertDialog_centerBright, R.drawable.popup_center_bright);
- int bottomBright = a.getResourceId(
- R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright);
- int bottomMedium = a.getResourceId(
- R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium);
-
- /*
- * We now set the background of all of the sections of the alert.
+ final int centerDark = a.getResourceId(
+ R.styleable.AlertDialog_centerDark, R.drawable.popup_center_dark);
+
+ /* We now set the background of all of the sections of the alert.
* First collect together each section that is being displayed along
* with whether it is on a light or dark background, then run through
* them setting their backgrounds. This is complicated because we need
* to correctly use the full, top, middle, and bottom graphics depending
* on how many views they are and where they appear.
*/
-
- View[] views = new View[4];
- boolean[] light = new boolean[4];
+
+ final View[] views = new View[4];
+ final boolean[] light = new boolean[4];
View lastView = null;
boolean lastLight = false;
-
+
int pos = 0;
if (hasTitle) {
views[pos] = topPanel;
light[pos] = false;
pos++;
}
-
+
/* The contentPanel displays either a custom text message or
* a ListView. If it's text we should use the dark background
* for ListView we should use the light background. If neither
* are there the contentPanel will be hidden so set it as null.
*/
- views[pos] = (contentPanel.getVisibility() == View.GONE)
- ? null : contentPanel;
+ views[pos] = contentPanel.getVisibility() == View.GONE ? null : contentPanel;
light[pos] = mListView != null;
pos++;
- if (customPanel != null) {
+
+ if (hasCustomView) {
views[pos] = customPanel;
light[pos] = mForceInverseBackground;
pos++;
}
+
if (hasButtons) {
views[pos] = buttonPanel;
light[pos] = true;
}
-
+
boolean setView = false;
- for (pos=0; pos<views.length; pos++) {
- View v = views[pos];
+ for (pos = 0; pos < views.length; pos++) {
+ final View v = views[pos];
if (v == null) {
continue;
}
+
if (lastView != null) {
if (!setView) {
lastView.setBackgroundResource(lastLight ? topBright : topDark);
@@ -674,23 +700,34 @@ public class AlertController {
}
setView = true;
}
+
lastView = v;
lastLight = light[pos];
}
-
+
if (lastView != null) {
if (setView) {
-
- /* ListViews will use the Bright background but buttons use
- * the Medium background.
- */
+ final int bottomBright = a.getResourceId(
+ R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright);
+ final int bottomMedium = a.getResourceId(
+ R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium);
+ final int bottomDark = a.getResourceId(
+ R.styleable.AlertDialog_bottomDark, R.drawable.popup_bottom_dark);
+
+ // ListViews will use the Bright background, but buttons use the
+ // Medium background.
lastView.setBackgroundResource(
lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark);
} else {
+ final int fullBright = a.getResourceId(
+ R.styleable.AlertDialog_fullBright, R.drawable.popup_full_bright);
+ final int fullDark = a.getResourceId(
+ R.styleable.AlertDialog_fullDark, R.drawable.popup_full_dark);
+
lastView.setBackgroundResource(lastLight ? fullBright : fullDark);
}
}
-
+
/* TODO: uncomment section below. The logic for this should be if
* it's a Contextual menu being displayed AND only a Cancel button
* is shown then do this.
@@ -715,12 +752,14 @@ public class AlertController {
mListView.addFooterView(buttonPanel);
*/
// }
-
- if ((mListView != null) && (mAdapter != null)) {
- mListView.setAdapter(mAdapter);
- if (mCheckedItem > -1) {
- mListView.setItemChecked(mCheckedItem, true);
- mListView.setSelection(mCheckedItem);
+
+ final ListView listView = mListView;
+ if (listView != null && mAdapter != null) {
+ listView.setAdapter(mAdapter);
+ final int checkedItem = mCheckedItem;
+ if (checkedItem > -1) {
+ listView.setItemChecked(checkedItem, true);
+ listView.setSelection(checkedItem);
}
}
}
@@ -736,8 +775,13 @@ public class AlertController {
super(context, attrs);
}
- public RecycleListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public RecycleListView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public RecycleListView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
@@ -769,6 +813,7 @@ public class AlertController {
public CharSequence[] mItems;
public ListAdapter mAdapter;
public DialogInterface.OnClickListener mOnClickListener;
+ public int mViewLayoutResId;
public View mView;
public int mViewSpacingLeft;
public int mViewSpacingTop;
@@ -854,8 +899,10 @@ public class AlertController {
} else {
dialog.setView(mView);
}
+ } else if (mViewLayoutResId != 0) {
+ dialog.setView(mViewLayoutResId);
}
-
+
/*
dialog.setCancelable(mCancelable);
dialog.setOnCancelListener(mOnCancelListener);
@@ -937,7 +984,8 @@ public class AlertController {
if (mOnClickListener != null) {
listView.setOnItemClickListener(new OnItemClickListener() {
- public void onItemClick(AdapterView parent, View v, int position, long id) {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
mOnClickListener.onClick(dialog.mDialogInterface, position);
if (!mIsSingleChoice) {
dialog.mDialogInterface.dismiss();
@@ -946,7 +994,8 @@ public class AlertController {
});
} else if (mOnCheckboxClickListener != null) {
listView.setOnItemClickListener(new OnItemClickListener() {
- public void onItemClick(AdapterView parent, View v, int position, long id) {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
if (mCheckedItems != null) {
mCheckedItems[position] = listView.isItemChecked(position);
}
diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
index 3d46cdd..83ad9dc 100644
--- a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
+++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
@@ -32,7 +32,6 @@ import android.util.TypedValue;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
-import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 16c41f3..cd75010 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -36,4 +36,6 @@ interface IAppOpsService {
List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops);
void setMode(int code, int uid, String packageName, int mode);
void resetAllModes();
+ int checkAudioOperation(int code, int stream, int uid, String packageName);
+ void setAudioRestriction(int code, int stream, int uid, int mode, in String[] exceptionPackages);
}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 43c4b49..1bb577b 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -19,22 +19,40 @@ package com.android.internal.app;
import com.android.internal.os.BatteryStatsImpl;
import android.os.WorkSource;
+import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.SignalStrength;
interface IBatteryStats {
- byte[] getStatistics();
- void noteStartWakelock(int uid, int pid, String name, int type);
- void noteStopWakelock(int uid, int pid, String name, int type);
-
- /* DO NOT CHANGE the position of noteStartSensor without updating
- SensorService.cpp */
+ // These first methods are also called by native code, so must
+ // be kept in sync with frameworks/native/include/binder/IBatteryStats.h
void noteStartSensor(int uid, int sensor);
-
- /* DO NOT CHANGE the position of noteStopSensor without updating
- SensorService.cpp */
void noteStopSensor(int uid, int sensor);
- void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, int type);
+ // Remaining methods are only used in Java.
+ byte[] getStatistics();
+
+ // Return the computed amount of time remaining on battery, in milliseconds.
+ // Returns -1 if nothing could be computed.
+ long computeBatteryTimeRemaining();
+
+ // Return the computed amount of time remaining to fully charge, in milliseconds.
+ // Returns -1 if nothing could be computed.
+ long computeChargeTimeRemaining();
+
+ void addIsolatedUid(int isolatedUid, int appUid);
+ void removeIsolatedUid(int isolatedUid, int appUid);
+
+ void noteEvent(int code, String name, int uid);
+
+ void noteStartWakelock(int uid, int pid, String name, String historyName,
+ int type, boolean unimportantForLogging);
+ void noteStopWakelock(int uid, int pid, String name, int type);
+
+ void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, String historyName,
+ int type, boolean unimportantForLogging);
+ void noteChangeWakelockFromSource(in WorkSource ws, int pid, String name, int type,
+ in WorkSource newWs, int newPid, String newName,
+ String newHistoryName, int newType, boolean newUnimportantForLogging);
void noteStopWakelockFromSource(in WorkSource ws, int pid, String name, int type);
void noteVibratorOn(int uid, long durationMillis);
@@ -46,6 +64,7 @@ interface IBatteryStats {
void noteScreenOff();
void noteInputEvent();
void noteUserActivity(int uid, int event);
+ void noteMobileRadioPowerState(int powerState, long timestampNs);
void notePhoneOn();
void notePhoneOff();
void notePhoneSignalStrength(in SignalStrength signalStrength);
@@ -56,8 +75,10 @@ interface IBatteryStats {
void noteWifiRunning(in WorkSource ws);
void noteWifiRunningChanged(in WorkSource oldWs, in WorkSource newWs);
void noteWifiStopped(in WorkSource ws);
+ void noteWifiState(int wifiState, String accessPoint);
void noteBluetoothOn();
void noteBluetoothOff();
+ void noteBluetoothState(int bluetoothState);
void noteFullWifiLockAcquired(int uid);
void noteFullWifiLockReleased(int uid);
void noteWifiScanStarted(int uid);
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
new file mode 100644
index 0000000..3219ddd
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.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 com.android.internal.app;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.internal.app.IVoiceInteractor;
+import android.service.voice.IVoiceInteractionService;
+import android.service.voice.IVoiceInteractionSession;
+
+interface IVoiceInteractionManagerService {
+ void startVoiceActivity(in Intent intent, String resolvedType, IVoiceInteractionService service,
+ in Bundle sessionArgs);
+ int deliverNewSession(IBinder token, IVoiceInteractionSession session,
+ IVoiceInteractor interactor);
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl
new file mode 100644
index 0000000..737906a
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.internal.app;
+
+import android.os.Bundle;
+
+import com.android.internal.app.IVoiceInteractorCallback;
+import com.android.internal.app.IVoiceInteractorRequest;
+
+/**
+ * IPC interface for an application to perform calls through a VoiceInteractor.
+ */
+interface IVoiceInteractor {
+ IVoiceInteractorRequest startConfirmation(String callingPackage,
+ IVoiceInteractorCallback callback, String prompt, in Bundle extras);
+ IVoiceInteractorRequest startCommand(String callingPackage,
+ IVoiceInteractorCallback callback, String command, in Bundle extras);
+ boolean[] supportsCommands(String callingPackage, in String[] commands);
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
new file mode 100644
index 0000000..c6f93e1
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.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 com.android.internal.app;
+
+import android.os.Bundle;
+
+import com.android.internal.app.IVoiceInteractorRequest;
+
+/**
+ * IPC interface for an application to receive callbacks from the voice system.
+ */
+oneway interface IVoiceInteractorCallback {
+ void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
+ in Bundle result);
+ void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, in Bundle result);
+ void deliverCancel(IVoiceInteractorRequest request);
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl b/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl
new file mode 100644
index 0000000..ce2902d
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.internal.app;
+
+/**
+ * IPC interface identifying a request from an application calling through an IVoiceInteractor.
+ */
+interface IVoiceInteractorRequest {
+ void cancel();
+}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
new file mode 100644
index 0000000..2f74372
--- /dev/null
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.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 com.android.internal.app;
+
+import android.app.Activity;
+import android.app.AppGlobals;
+import android.os.Bundle;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.app.ActivityManagerNative;
+import android.os.RemoteException;
+import android.util.Slog;
+import java.util.List;
+import java.util.Set;
+
+
+
+
+/*
+ * This is used in conjunction with DevicePolicyManager.setForwardingIntents to enable intents to be
+ * passed in and out of a managed profile.
+ */
+
+public class IntentForwarderActivity extends Activity {
+
+ public static String TAG = "IntentForwarderActivity";
+
+ public static String FORWARD_INTENT_TO_USER_OWNER
+ = "com.android.internal.app.ForwardIntentToUserOwner";
+
+ public static String FORWARD_INTENT_TO_MANAGED_PROFILE
+ = "com.android.internal.app.ForwardIntentToManagedProfile";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intentReceived = getIntent();
+
+ String className = intentReceived.getComponent().getClassName();
+ final UserHandle userDest;
+
+ if (className.equals(FORWARD_INTENT_TO_USER_OWNER)) {
+ userDest = UserHandle.OWNER;
+ } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) {
+ userDest = getManagedProfile();
+ } else {
+ Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly");
+ userDest = null;
+ }
+ if (userDest == null) { // This covers the case where there is no managed profile.
+ finish();
+ return;
+ }
+ Intent newIntent = new Intent(intentReceived);
+ newIntent.setComponent(null);
+ newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
+ |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+ int callingUserId = getUserId();
+ IPackageManager ipm = AppGlobals.getPackageManager();
+ String resolvedType = newIntent.resolveTypeIfNeeded(getContentResolver());
+ boolean canForward = false;
+ try {
+ canForward = ipm.canForwardTo(newIntent, resolvedType, callingUserId,
+ userDest.getIdentifier());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "PackageManagerService is dead?");
+ }
+ if (canForward) {
+ startActivityAsUser(newIntent, userDest);
+ } else {
+ Slog.wtf(TAG, "the intent: " + newIntent + "cannot be forwarded from user "
+ + callingUserId + " to user " + userDest.getIdentifier());
+ }
+ finish();
+ }
+
+ /**
+ * Returns the managed profile for this device or null if there is no managed
+ * profile.
+ *
+ * TODO: Remove the assumption that there is only one managed profile
+ * on the device.
+ */
+ private UserHandle getManagedProfile() {
+ UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+ List<UserInfo> relatedUsers = userManager.getProfiles(UserHandle.USER_OWNER);
+ for (UserInfo userInfo : relatedUsers) {
+ if (userInfo.isManagedProfile()) return new UserHandle(userInfo.id);
+ }
+ Slog.wtf(TAG, FORWARD_INTENT_TO_MANAGED_PROFILE
+ + " has been called, but there is no managed profile");
+ return null;
+ }
+}
diff --git a/core/java/com/android/internal/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java
index 043964f..ec2d654 100644
--- a/core/java/com/android/internal/app/LocalePicker.java
+++ b/core/java/com/android/internal/app/LocalePicker.java
@@ -117,7 +117,7 @@ public class LocalePicker extends ListFragment {
/** - TODO: Enable when zz_ZY Pseudolocale is complete
* if (!localeList.contains("zz_ZY")) {
* localeList.add("zz_ZY");
- * }
+ * }
*/
}
String[] locales = new String[localeList.size()];
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
index ae362af..237feed 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
@@ -21,7 +21,6 @@ import android.app.DialogFragment;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
-import android.view.View.OnClickListener;
/**
* Media route chooser dialog fragment.
diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialog.java b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
index 8fc99c7..b0e0373 100644
--- a/core/java/com/android/internal/app/MediaRouteControllerDialog.java
+++ b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
@@ -256,13 +256,13 @@ public class MediaRouteControllerDialog extends Dialog {
private Drawable getIconDrawable() {
if (mRoute.isConnecting()) {
if (mMediaRouteConnectingDrawable == null) {
- mMediaRouteConnectingDrawable = getContext().getResources().getDrawable(
+ mMediaRouteConnectingDrawable = getContext().getDrawable(
R.drawable.ic_media_route_connecting_holo_dark);
}
return mMediaRouteConnectingDrawable;
} else {
if (mMediaRouteOnDrawable == null) {
- mMediaRouteOnDrawable = getContext().getResources().getDrawable(
+ mMediaRouteOnDrawable = getContext().getDrawable(
R.drawable.ic_media_route_on_holo_dark);
}
return mMediaRouteOnDrawable;
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index 40a705c..8cdaf91 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -18,7 +18,6 @@ package com.android.internal.app;
import android.app.Activity;
import android.content.ActivityNotFoundException;
-import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.provider.Settings;
@@ -26,19 +25,15 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.text.method.AllCapsTransformationMethod;
-import android.text.method.TransformationMethod;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.View;
-import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnticipateOvershootInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.TextView;
-import android.widget.Toast;
public class PlatLogoActivity extends Activity {
FrameLayout mContent;
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index a87992a..882bec9 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -29,8 +29,11 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.webkit.WebViewFactory;
-import com.android.internal.util.ArrayUtils;
+
+import com.android.internal.util.GrowingArrayUtils;
+
import dalvik.system.VMRuntime;
+import libcore.util.EmptyArray;
import java.io.IOException;
import java.io.InputStream;
@@ -171,7 +174,7 @@ public final class ProcessStats implements Parcelable {
static final String CSV_SEP = "\t";
// Current version of the parcel format.
- private static final int PARCEL_VERSION = 13;
+ private static final int PARCEL_VERSION = 14;
// In-memory Parcel magic number, used to detect attempts to unmarshall bad data
private static final int MAGIC = 0x50535453;
@@ -189,7 +192,8 @@ public final class ProcessStats implements Parcelable {
public String mTimePeriodStartClockStr;
public int mFlags;
- public final ProcessMap<PackageState> mPackages = new ProcessMap<PackageState>();
+ public final ProcessMap<SparseArray<PackageState>> mPackages
+ = new ProcessMap<SparseArray<PackageState>>();
public final ProcessMap<ProcessState> mProcesses = new ProcessMap<ProcessState>();
public final long[] mMemFactorDurations = new long[ADJ_COUNT];
@@ -227,40 +231,45 @@ public final class ProcessStats implements Parcelable {
}
public void add(ProcessStats other) {
- ArrayMap<String, SparseArray<PackageState>> pkgMap = other.mPackages.getMap();
+ ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = other.mPackages.getMap();
for (int ip=0; ip<pkgMap.size(); ip++) {
- String pkgName = pkgMap.keyAt(ip);
- SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final String pkgName = pkgMap.keyAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
for (int iu=0; iu<uids.size(); iu++) {
- int uid = uids.keyAt(iu);
- PackageState otherState = uids.valueAt(iu);
- final int NPROCS = otherState.mProcesses.size();
- final int NSRVS = otherState.mServices.size();
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState otherProc = otherState.mProcesses.valueAt(iproc);
- if (otherProc.mCommonProcess != otherProc) {
- if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid
- + " proc " + otherProc.mName);
- ProcessState thisProc = getProcessStateLocked(pkgName, uid,
- otherProc.mName);
- if (thisProc.mCommonProcess == thisProc) {
- if (DEBUG) Slog.d(TAG, "Existing process is single-package, splitting");
- thisProc.mMultiPackage = true;
- long now = SystemClock.uptimeMillis();
- final PackageState pkgState = getPackageStateLocked(pkgName, uid);
- thisProc = thisProc.clone(thisProc.mPackage, now);
- pkgState.mProcesses.put(thisProc.mName, thisProc);
+ final int uid = uids.keyAt(iu);
+ final SparseArray<PackageState> versions = uids.valueAt(iu);
+ for (int iv=0; iv<versions.size(); iv++) {
+ final int vers = versions.keyAt(iv);
+ final PackageState otherState = versions.valueAt(iv);
+ final int NPROCS = otherState.mProcesses.size();
+ final int NSRVS = otherState.mServices.size();
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState otherProc = otherState.mProcesses.valueAt(iproc);
+ if (otherProc.mCommonProcess != otherProc) {
+ if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid
+ + " vers " + vers + " proc " + otherProc.mName);
+ ProcessState thisProc = getProcessStateLocked(pkgName, uid, vers,
+ otherProc.mName);
+ if (thisProc.mCommonProcess == thisProc) {
+ if (DEBUG) Slog.d(TAG, "Existing process is single-package, splitting");
+ thisProc.mMultiPackage = true;
+ long now = SystemClock.uptimeMillis();
+ final PackageState pkgState = getPackageStateLocked(pkgName, uid,
+ vers);
+ thisProc = thisProc.clone(thisProc.mPackage, now);
+ pkgState.mProcesses.put(thisProc.mName, thisProc);
+ }
+ thisProc.add(otherProc);
}
- thisProc.add(otherProc);
}
- }
- for (int isvc=0; isvc<NSRVS; isvc++) {
- ServiceState otherSvc = otherState.mServices.valueAt(isvc);
- if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid
- + " service " + otherSvc.mName);
- ServiceState thisSvc = getServiceStateLocked(pkgName, uid,
- otherSvc.mProcessName, otherSvc.mName);
- thisSvc.add(otherSvc);
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ ServiceState otherSvc = otherState.mServices.valueAt(isvc);
+ if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid
+ + " service " + otherSvc.mName);
+ ServiceState thisSvc = getServiceStateLocked(pkgName, uid, vers,
+ otherSvc.mProcessName, otherSvc.mName);
+ thisSvc.add(otherSvc);
+ }
}
}
}
@@ -275,9 +284,11 @@ public final class ProcessStats implements Parcelable {
if (DEBUG) Slog.d(TAG, "Adding uid " + uid + " proc " + otherProc.mName);
if (thisProc == null) {
if (DEBUG) Slog.d(TAG, "Creating new process!");
- thisProc = new ProcessState(this, otherProc.mPackage, uid, otherProc.mName);
+ thisProc = new ProcessState(this, otherProc.mPackage, uid, otherProc.mVersion,
+ otherProc.mName);
mProcesses.put(otherProc.mName, uid, thisProc);
- PackageState thisState = getPackageStateLocked(otherProc.mPackage, uid);
+ PackageState thisState = getPackageStateLocked(otherProc.mPackage, uid,
+ otherProc.mVersion);
if (!thisState.mProcesses.containsKey(otherProc.mName)) {
thisState.mProcesses.put(otherProc.mName, thisProc);
}
@@ -440,7 +451,7 @@ public final class ProcessStats implements Parcelable {
}
static void dumpServiceTimeCheckin(PrintWriter pw, String label, String packageName,
- int uid, String serviceName, ServiceState svc, int serviceType, int opCount,
+ int uid, int vers, String serviceName, ServiceState svc, int serviceType, int opCount,
int curState, long curStartTime, long now) {
if (opCount <= 0) {
return;
@@ -451,6 +462,8 @@ public final class ProcessStats implements Parcelable {
pw.print(",");
pw.print(uid);
pw.print(",");
+ pw.print(vers);
+ pw.print(",");
pw.print(serviceName);
pw.print(",");
pw.print(opCount);
@@ -775,7 +788,7 @@ public final class ProcessStats implements Parcelable {
static void dumpProcessSummaryLocked(PrintWriter pw, String prefix,
ArrayList<ProcessState> procs, int[] screenStates, int[] memStates, int[] procStates,
- long now, long totalTime) {
+ boolean inclUidVers, long now, long totalTime) {
for (int i=procs.size()-1; i>=0; i--) {
ProcessState proc = procs.get(i);
pw.print(prefix);
@@ -783,6 +796,8 @@ public final class ProcessStats implements Parcelable {
pw.print(proc.mName);
pw.print(" / ");
UserHandle.formatUid(pw, proc.mUid);
+ pw.print(" / v");
+ pw.print(proc.mVersion);
pw.println(":");
dumpProcessSummaryDetails(pw, proc, prefix, " TOTAL: ", screenStates, memStates,
procStates, now, totalTime, true);
@@ -869,6 +884,8 @@ public final class ProcessStats implements Parcelable {
pw.print("process");
pw.print(CSV_SEP);
pw.print("uid");
+ pw.print(CSV_SEP);
+ pw.print("vers");
dumpStateHeadersCsv(pw, CSV_SEP, sepScreenStates ? screenStates : null,
sepMemStates ? memStates : null,
sepProcStates ? procStates : null);
@@ -878,6 +895,8 @@ public final class ProcessStats implements Parcelable {
pw.print(proc.mName);
pw.print(CSV_SEP);
UserHandle.formatUid(pw, proc.mUid);
+ pw.print(CSV_SEP);
+ pw.print(proc.mVersion);
dumpProcessStateCsv(pw, proc, sepScreenStates, screenStates,
sepMemStates, memStates, sepProcStates, procStates, now);
pw.println();
@@ -979,53 +998,88 @@ public final class ProcessStats implements Parcelable {
public void resetSafely() {
if (DEBUG) Slog.d(TAG, "Safely resetting state of " + mTimePeriodStartClockStr);
resetCommon();
- long now = SystemClock.uptimeMillis();
- ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+
+ // First initialize use count of all common processes.
+ final long now = SystemClock.uptimeMillis();
+ final ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
for (int ip=procMap.size()-1; ip>=0; ip--) {
- SparseArray<ProcessState> uids = procMap.valueAt(ip);
+ final SparseArray<ProcessState> uids = procMap.valueAt(ip);
for (int iu=uids.size()-1; iu>=0; iu--) {
- ProcessState ps = uids.valueAt(iu);
- if (ps.isInUse()) {
- uids.valueAt(iu).resetSafely(now);
- } else {
- uids.valueAt(iu).makeDead();
+ uids.valueAt(iu).mTmpNumInUse = 0;
+ }
+ }
+
+ // Next reset or prune all per-package processes, and for the ones that are reset
+ // track this back to the common processes.
+ final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
+ for (int ip=pkgMap.size()-1; ip>=0; ip--) {
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
+ for (int iu=uids.size()-1; iu>=0; iu--) {
+ final SparseArray<PackageState> vpkgs = uids.valueAt(iu);
+ for (int iv=vpkgs.size()-1; iv>=0; iv--) {
+ final PackageState pkgState = vpkgs.valueAt(iv);
+ for (int iproc=pkgState.mProcesses.size()-1; iproc>=0; iproc--) {
+ final ProcessState ps = pkgState.mProcesses.valueAt(iproc);
+ if (ps.isInUse()) {
+ ps.resetSafely(now);
+ ps.mCommonProcess.mTmpNumInUse++;
+ ps.mCommonProcess.mTmpFoundSubProc = ps;
+ } else {
+ pkgState.mProcesses.valueAt(iproc).makeDead();
+ pkgState.mProcesses.removeAt(iproc);
+ }
+ }
+ for (int isvc=pkgState.mServices.size()-1; isvc>=0; isvc--) {
+ final ServiceState ss = pkgState.mServices.valueAt(isvc);
+ if (ss.isInUse()) {
+ ss.resetSafely(now);
+ } else {
+ pkgState.mServices.removeAt(isvc);
+ }
+ }
+ if (pkgState.mProcesses.size() <= 0 && pkgState.mServices.size() <= 0) {
+ vpkgs.removeAt(iv);
+ }
+ }
+ if (vpkgs.size() <= 0) {
uids.removeAt(iu);
}
}
if (uids.size() <= 0) {
- procMap.removeAt(ip);
+ pkgMap.removeAt(ip);
}
}
- ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
- for (int ip=pkgMap.size()-1; ip>=0; ip--) {
- SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+
+ // Finally prune out any common processes that are no longer in use.
+ for (int ip=procMap.size()-1; ip>=0; ip--) {
+ final SparseArray<ProcessState> uids = procMap.valueAt(ip);
for (int iu=uids.size()-1; iu>=0; iu--) {
- PackageState pkgState = uids.valueAt(iu);
- for (int iproc=pkgState.mProcesses.size()-1; iproc>=0; iproc--) {
- ProcessState ps = pkgState.mProcesses.valueAt(iproc);
- if (ps.isInUse() || ps.mCommonProcess.isInUse()) {
- pkgState.mProcesses.valueAt(iproc).resetSafely(now);
- } else {
- pkgState.mProcesses.valueAt(iproc).makeDead();
- pkgState.mProcesses.removeAt(iproc);
- }
- }
- for (int isvc=pkgState.mServices.size()-1; isvc>=0; isvc--) {
- ServiceState ss = pkgState.mServices.valueAt(isvc);
- if (ss.isInUse()) {
- pkgState.mServices.valueAt(isvc).resetSafely(now);
+ ProcessState ps = uids.valueAt(iu);
+ if (ps.isInUse() || ps.mTmpNumInUse > 0) {
+ // If this is a process for multiple packages, we could at this point
+ // be back down to one package. In that case, we want to revert back
+ // to a single shared ProcessState. We can do this by converting the
+ // current package-specific ProcessState up to the shared ProcessState,
+ // throwing away the current one we have here (because nobody else is
+ // using it).
+ if (!ps.mActive && ps.mMultiPackage && ps.mTmpNumInUse == 1) {
+ // Here we go...
+ ps = ps.mTmpFoundSubProc;
+ ps.mCommonProcess = ps;
+ uids.setValueAt(iu, ps);
} else {
- pkgState.mServices.removeAt(isvc);
+ ps.resetSafely(now);
}
- }
- if (pkgState.mProcesses.size() <= 0 && pkgState.mServices.size() <= 0) {
+ } else {
+ ps.makeDead();
uids.removeAt(iu);
}
}
if (uids.size() <= 0) {
- pkgMap.removeAt(ip);
+ procMap.removeAt(ip);
}
}
+
mStartTime = now;
if (DEBUG) Slog.d(TAG, "State reset; now " + mTimePeriodStartClockStr);
}
@@ -1193,23 +1247,27 @@ public final class ProcessStats implements Parcelable {
uids.valueAt(iu).commitStateTime(now);
}
}
- ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
+ final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
final int NPKG = pkgMap.size();
for (int ip=0; ip<NPKG; ip++) {
- SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
final int NUID = uids.size();
for (int iu=0; iu<NUID; iu++) {
- PackageState pkgState = uids.valueAt(iu);
- final int NPROCS = pkgState.mProcesses.size();
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- if (proc.mCommonProcess != proc) {
- proc.commitStateTime(now);
+ final SparseArray<PackageState> vpkgs = uids.valueAt(iu);
+ final int NVERS = vpkgs.size();
+ for (int iv=0; iv<NVERS; iv++) {
+ PackageState pkgState = vpkgs.valueAt(iv);
+ final int NPROCS = pkgState.mProcesses.size();
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ if (proc.mCommonProcess != proc) {
+ proc.commitStateTime(now);
+ }
+ }
+ final int NSRVS = pkgState.mServices.size();
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ pkgState.mServices.valueAt(isvc).commitStateTime(now);
}
- }
- final int NSRVS = pkgState.mServices.size();
- for (int isvc=0; isvc<NSRVS; isvc++) {
- pkgState.mServices.valueAt(isvc).commitStateTime(now);
}
}
}
@@ -1239,46 +1297,53 @@ public final class ProcessStats implements Parcelable {
out.writeInt(NPROC);
for (int ip=0; ip<NPROC; ip++) {
writeCommonString(out, procMap.keyAt(ip));
- SparseArray<ProcessState> uids = procMap.valueAt(ip);
+ final SparseArray<ProcessState> uids = procMap.valueAt(ip);
final int NUID = uids.size();
out.writeInt(NUID);
for (int iu=0; iu<NUID; iu++) {
out.writeInt(uids.keyAt(iu));
- ProcessState proc = uids.valueAt(iu);
+ final ProcessState proc = uids.valueAt(iu);
writeCommonString(out, proc.mPackage);
+ out.writeInt(proc.mVersion);
proc.writeToParcel(out, now);
}
}
out.writeInt(NPKG);
for (int ip=0; ip<NPKG; ip++) {
writeCommonString(out, pkgMap.keyAt(ip));
- SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
final int NUID = uids.size();
out.writeInt(NUID);
for (int iu=0; iu<NUID; iu++) {
out.writeInt(uids.keyAt(iu));
- PackageState pkgState = uids.valueAt(iu);
- final int NPROCS = pkgState.mProcesses.size();
- out.writeInt(NPROCS);
- for (int iproc=0; iproc<NPROCS; iproc++) {
- writeCommonString(out, pkgState.mProcesses.keyAt(iproc));
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- if (proc.mCommonProcess == proc) {
- // This is the same as the common process we wrote above.
- out.writeInt(0);
- } else {
- // There is separate data for this package's process.
- out.writeInt(1);
- proc.writeToParcel(out, now);
+ final SparseArray<PackageState> vpkgs = uids.valueAt(iu);
+ final int NVERS = vpkgs.size();
+ out.writeInt(NVERS);
+ for (int iv=0; iv<NVERS; iv++) {
+ out.writeInt(vpkgs.keyAt(iv));
+ final PackageState pkgState = vpkgs.valueAt(iv);
+ final int NPROCS = pkgState.mProcesses.size();
+ out.writeInt(NPROCS);
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ writeCommonString(out, pkgState.mProcesses.keyAt(iproc));
+ final ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ if (proc.mCommonProcess == proc) {
+ // This is the same as the common process we wrote above.
+ out.writeInt(0);
+ } else {
+ // There is separate data for this package's process.
+ out.writeInt(1);
+ proc.writeToParcel(out, now);
+ }
+ }
+ final int NSRVS = pkgState.mServices.size();
+ out.writeInt(NSRVS);
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ out.writeString(pkgState.mServices.keyAt(isvc));
+ final ServiceState svc = pkgState.mServices.valueAt(isvc);
+ writeCommonString(out, svc.mProcessName);
+ svc.writeToParcel(out, now);
}
- }
- final int NSRVS = pkgState.mServices.size();
- out.writeInt(NSRVS);
- for (int isvc=0; isvc<NSRVS; isvc++) {
- out.writeString(pkgState.mServices.keyAt(isvc));
- ServiceState svc = pkgState.mServices.valueAt(isvc);
- writeCommonString(out, svc.mProcessName);
- svc.writeToParcel(out, now);
}
}
}
@@ -1396,7 +1461,7 @@ public final class ProcessStats implements Parcelable {
}
while (NPROC > 0) {
NPROC--;
- String procName = readCommonString(in, version);
+ final String procName = readCommonString(in, version);
if (procName == null) {
mReadError = "bad process name";
return;
@@ -1408,23 +1473,24 @@ public final class ProcessStats implements Parcelable {
}
while (NUID > 0) {
NUID--;
- int uid = in.readInt();
+ final int uid = in.readInt();
if (uid < 0) {
mReadError = "bad uid: " + uid;
return;
}
- String pkgName = readCommonString(in, version);
+ final String pkgName = readCommonString(in, version);
if (pkgName == null) {
mReadError = "bad process package name";
return;
}
+ final int vers = in.readInt();
ProcessState proc = hadData ? mProcesses.get(procName, uid) : null;
if (proc != null) {
if (!proc.readFromParcel(in, false)) {
return;
}
} else {
- proc = new ProcessState(this, pkgName, uid, procName);
+ proc = new ProcessState(this, pkgName, uid, vers, procName);
if (!proc.readFromParcel(in, true)) {
return;
}
@@ -1444,7 +1510,7 @@ public final class ProcessStats implements Parcelable {
}
while (NPKG > 0) {
NPKG--;
- String pkgName = readCommonString(in, version);
+ final String pkgName = readCommonString(in, version);
if (pkgName == null) {
mReadError = "bad package name";
return;
@@ -1456,83 +1522,98 @@ public final class ProcessStats implements Parcelable {
}
while (NUID > 0) {
NUID--;
- int uid = in.readInt();
+ final int uid = in.readInt();
if (uid < 0) {
mReadError = "bad uid: " + uid;
return;
}
- PackageState pkgState = new PackageState(pkgName, uid);
- mPackages.put(pkgName, uid, pkgState);
- int NPROCS = in.readInt();
- if (NPROCS < 0) {
- mReadError = "bad package process count: " + NPROCS;
+ int NVERS = in.readInt();
+ if (NVERS < 0) {
+ mReadError = "bad versions count: " + NVERS;
return;
}
- while (NPROCS > 0) {
- NPROCS--;
- String procName = readCommonString(in, version);
- if (procName == null) {
- mReadError = "bad package process name";
- return;
+ while (NVERS > 0) {
+ NVERS--;
+ final int vers = in.readInt();
+ PackageState pkgState = new PackageState(pkgName, uid);
+ SparseArray<PackageState> vpkg = mPackages.get(pkgName, uid);
+ if (vpkg == null) {
+ vpkg = new SparseArray<PackageState>();
+ mPackages.put(pkgName, uid, vpkg);
}
- int hasProc = in.readInt();
- if (DEBUG_PARCEL) Slog.d(TAG, "Reading package " + pkgName + " " + uid
- + " process " + procName + " hasProc=" + hasProc);
- ProcessState commonProc = mProcesses.get(procName, uid);
- if (DEBUG_PARCEL) Slog.d(TAG, "Got common proc " + procName + " " + uid
- + ": " + commonProc);
- if (commonProc == null) {
- mReadError = "no common proc: " + procName;
+ vpkg.put(vers, pkgState);
+ int NPROCS = in.readInt();
+ if (NPROCS < 0) {
+ mReadError = "bad package process count: " + NPROCS;
return;
}
- if (hasProc != 0) {
- // The process for this package is unique to the package; we
- // need to load it. We don't need to do anything about it if
- // it is not unique because if someone later looks for it
- // they will find and use it from the global procs.
- ProcessState proc = hadData ? pkgState.mProcesses.get(procName) : null;
- if (proc != null) {
- if (!proc.readFromParcel(in, false)) {
- return;
+ while (NPROCS > 0) {
+ NPROCS--;
+ String procName = readCommonString(in, version);
+ if (procName == null) {
+ mReadError = "bad package process name";
+ return;
+ }
+ int hasProc = in.readInt();
+ if (DEBUG_PARCEL) Slog.d(TAG, "Reading package " + pkgName + " " + uid
+ + " process " + procName + " hasProc=" + hasProc);
+ ProcessState commonProc = mProcesses.get(procName, uid);
+ if (DEBUG_PARCEL) Slog.d(TAG, "Got common proc " + procName + " " + uid
+ + ": " + commonProc);
+ if (commonProc == null) {
+ mReadError = "no common proc: " + procName;
+ return;
+ }
+ if (hasProc != 0) {
+ // The process for this package is unique to the package; we
+ // need to load it. We don't need to do anything about it if
+ // it is not unique because if someone later looks for it
+ // they will find and use it from the global procs.
+ ProcessState proc = hadData ? pkgState.mProcesses.get(procName) : null;
+ if (proc != null) {
+ if (!proc.readFromParcel(in, false)) {
+ return;
+ }
+ } else {
+ proc = new ProcessState(commonProc, pkgName, uid, vers, procName,
+ 0);
+ if (!proc.readFromParcel(in, true)) {
+ return;
+ }
}
+ if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: "
+ + procName + " " + uid + " " + proc);
+ pkgState.mProcesses.put(procName, proc);
} else {
- proc = new ProcessState(commonProc, pkgName, uid, procName, 0);
- if (!proc.readFromParcel(in, true)) {
- return;
- }
+ if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: "
+ + procName + " " + uid + " " + commonProc);
+ pkgState.mProcesses.put(procName, commonProc);
}
- if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: "
- + procName + " " + uid + " " + proc);
- pkgState.mProcesses.put(procName, proc);
- } else {
- if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: "
- + procName + " " + uid + " " + commonProc);
- pkgState.mProcesses.put(procName, commonProc);
}
- }
- int NSRVS = in.readInt();
- if (NSRVS < 0) {
- mReadError = "bad package service count: " + NSRVS;
- return;
- }
- while (NSRVS > 0) {
- NSRVS--;
- String serviceName = in.readString();
- if (serviceName == null) {
- mReadError = "bad package service name";
+ int NSRVS = in.readInt();
+ if (NSRVS < 0) {
+ mReadError = "bad package service count: " + NSRVS;
return;
}
- String processName = version > 9 ? readCommonString(in, version) : null;
- ServiceState serv = hadData ? pkgState.mServices.get(serviceName) : null;
- if (serv == null) {
- serv = new ServiceState(this, pkgName, serviceName, processName, null);
- }
- if (!serv.readFromParcel(in)) {
- return;
+ while (NSRVS > 0) {
+ NSRVS--;
+ String serviceName = in.readString();
+ if (serviceName == null) {
+ mReadError = "bad package service name";
+ return;
+ }
+ String processName = version > 9 ? readCommonString(in, version) : null;
+ ServiceState serv = hadData ? pkgState.mServices.get(serviceName) : null;
+ if (serv == null) {
+ serv = new ServiceState(this, pkgName, serviceName, processName, null);
+ }
+ if (!serv.readFromParcel(in)) {
+ return;
+ }
+ if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " service: "
+ + serviceName + " " + uid + " " + serv);
+ pkgState.mServices.put(serviceName, serv);
}
- if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " service: "
- + serviceName + " " + uid + " " + serv);
- pkgState.mServices.put(serviceName, serv);
}
}
}
@@ -1543,21 +1624,10 @@ public final class ProcessStats implements Parcelable {
}
int addLongData(int index, int type, int num) {
- int tableLen = mAddLongTable != null ? mAddLongTable.length : 0;
- if (mAddLongTableSize >= tableLen) {
- int newSize = ArrayUtils.idealIntArraySize(tableLen + 1);
- int[] newTable = new int[newSize];
- if (tableLen > 0) {
- System.arraycopy(mAddLongTable, 0, newTable, 0, tableLen);
- }
- mAddLongTable = newTable;
- }
- if (mAddLongTableSize > 0 && mAddLongTableSize - index != 0) {
- System.arraycopy(mAddLongTable, index, mAddLongTable, index + 1,
- mAddLongTableSize - index);
- }
int off = allocLongData(num);
- mAddLongTable[index] = type | off;
+ mAddLongTable = GrowingArrayUtils.insert(
+ mAddLongTable != null ? mAddLongTable : EmptyArray.INT,
+ mAddLongTableSize, index, type | off);
mAddLongTableSize++;
return off;
}
@@ -1627,30 +1697,36 @@ public final class ProcessStats implements Parcelable {
return ~lo; // value not present
}
- public PackageState getPackageStateLocked(String packageName, int uid) {
- PackageState as = mPackages.get(packageName, uid);
+ public PackageState getPackageStateLocked(String packageName, int uid, int vers) {
+ SparseArray<PackageState> vpkg = mPackages.get(packageName, uid);
+ if (vpkg == null) {
+ vpkg = new SparseArray<PackageState>();
+ mPackages.put(packageName, uid, vpkg);
+ }
+ PackageState as = vpkg.get(vers);
if (as != null) {
return as;
}
as = new PackageState(packageName, uid);
- mPackages.put(packageName, uid, as);
+ vpkg.put(vers, as);
return as;
}
- public ProcessState getProcessStateLocked(String packageName, int uid, String processName) {
- final PackageState pkgState = getPackageStateLocked(packageName, uid);
+ public ProcessState getProcessStateLocked(String packageName, int uid, int vers,
+ String processName) {
+ final PackageState pkgState = getPackageStateLocked(packageName, uid, vers);
ProcessState ps = pkgState.mProcesses.get(processName);
if (ps != null) {
return ps;
}
ProcessState commonProc = mProcesses.get(processName, uid);
if (commonProc == null) {
- commonProc = new ProcessState(this, packageName, uid, processName);
+ commonProc = new ProcessState(this, packageName, uid, vers, processName);
mProcesses.put(processName, uid, commonProc);
if (DEBUG) Slog.d(TAG, "GETPROC created new common " + commonProc);
}
if (!commonProc.mMultiPackage) {
- if (packageName.equals(commonProc.mPackage)) {
+ if (packageName.equals(commonProc.mPackage) && vers == commonProc.mVersion) {
// This common process is not in use by multiple packages, and
// is for the calling package, so we can just use it directly.
ps = commonProc;
@@ -1668,7 +1744,8 @@ public final class ProcessStats implements Parcelable {
long now = SystemClock.uptimeMillis();
// First let's make a copy of the current process state and put
// that under the now unique state for its original package name.
- final PackageState commonPkgState = getPackageStateLocked(commonProc.mPackage, uid);
+ final PackageState commonPkgState = getPackageStateLocked(commonProc.mPackage,
+ uid, commonProc.mVersion);
if (commonPkgState != null) {
ProcessState cloned = commonProc.clone(commonProc.mPackage, now);
if (DEBUG) Slog.d(TAG, "GETPROC setting clone to pkg " + commonProc.mPackage
@@ -1691,13 +1768,13 @@ public final class ProcessStats implements Parcelable {
+ "/" + uid + " for proc " + commonProc.mName);
}
// And now make a fresh new process state for the new package name.
- ps = new ProcessState(commonProc, packageName, uid, processName, now);
+ ps = new ProcessState(commonProc, packageName, uid, vers, processName, now);
if (DEBUG) Slog.d(TAG, "GETPROC created new pkg " + ps);
}
} else {
// The common process is for multiple packages, we need to create a
// separate object for the per-package data.
- ps = new ProcessState(commonProc, packageName, uid, processName,
+ ps = new ProcessState(commonProc, packageName, uid, vers, processName,
SystemClock.uptimeMillis());
if (DEBUG) Slog.d(TAG, "GETPROC created new pkg " + ps);
}
@@ -1706,16 +1783,16 @@ public final class ProcessStats implements Parcelable {
return ps;
}
- public ProcessStats.ServiceState getServiceStateLocked(String packageName, int uid,
+ public ProcessStats.ServiceState getServiceStateLocked(String packageName, int uid, int vers,
String processName, String className) {
- final ProcessStats.PackageState as = getPackageStateLocked(packageName, uid);
+ final ProcessStats.PackageState as = getPackageStateLocked(packageName, uid, vers);
ProcessStats.ServiceState ss = as.mServices.get(className);
if (ss != null) {
if (DEBUG) Slog.d(TAG, "GETSVC: returning existing " + ss);
return ss;
}
final ProcessStats.ProcessState ps = processName != null
- ? getProcessStateLocked(packageName, uid, processName) : null;
+ ? getProcessStateLocked(packageName, uid, vers, processName) : null;
ss = new ProcessStats.ServiceState(this, packageName, className, processName, ps);
as.mServices.put(className, ss);
if (DEBUG) Slog.d(TAG, "GETSVC: creating " + ss + " in " + ps);
@@ -1756,119 +1833,124 @@ public final class ProcessStats implements Parcelable {
boolean dumpAll, boolean activeOnly) {
long totalTime = dumpSingleTime(null, null, mMemFactorDurations, mMemFactor,
mStartTime, now);
- ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
+ ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
boolean printedHeader = false;
boolean sepNeeded = false;
for (int ip=0; ip<pkgMap.size(); ip++) {
final String pkgName = pkgMap.keyAt(ip);
- final SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
for (int iu=0; iu<uids.size(); iu++) {
final int uid = uids.keyAt(iu);
- final PackageState pkgState = uids.valueAt(iu);
- final int NPROCS = pkgState.mProcesses.size();
- final int NSRVS = pkgState.mServices.size();
- final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
- if (!pkgMatch) {
- boolean procMatch = false;
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- if (reqPackage.equals(proc.mName)) {
- procMatch = true;
- break;
+ final SparseArray<PackageState> vpkgs = uids.valueAt(iu);
+ for (int iv=0; iv<vpkgs.size(); iv++) {
+ final int vers = vpkgs.keyAt(iv);
+ final PackageState pkgState = vpkgs.valueAt(iv);
+ final int NPROCS = pkgState.mProcesses.size();
+ final int NSRVS = pkgState.mServices.size();
+ final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
+ if (!pkgMatch) {
+ boolean procMatch = false;
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ if (reqPackage.equals(proc.mName)) {
+ procMatch = true;
+ break;
+ }
+ }
+ if (!procMatch) {
+ continue;
}
}
- if (!procMatch) {
- continue;
+ if (NPROCS > 0 || NSRVS > 0) {
+ if (!printedHeader) {
+ pw.println("Per-Package Stats:");
+ printedHeader = true;
+ sepNeeded = true;
+ }
+ pw.print(" * "); pw.print(pkgName); pw.print(" / ");
+ UserHandle.formatUid(pw, uid); pw.print(" / v");
+ pw.print(vers); pw.println(":");
}
- }
- if (NPROCS > 0 || NSRVS > 0) {
- if (!printedHeader) {
- pw.println("Per-Package Stats:");
- printedHeader = true;
- sepNeeded = true;
+ if (!dumpSummary || dumpAll) {
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ if (!pkgMatch && !reqPackage.equals(proc.mName)) {
+ continue;
+ }
+ if (activeOnly && !proc.isInUse()) {
+ pw.print(" (Not active: ");
+ pw.print(pkgState.mProcesses.keyAt(iproc)); pw.println(")");
+ continue;
+ }
+ pw.print(" Process ");
+ pw.print(pkgState.mProcesses.keyAt(iproc));
+ if (proc.mCommonProcess.mMultiPackage) {
+ pw.print(" (multi, ");
+ } else {
+ pw.print(" (unique, ");
+ }
+ pw.print(proc.mDurationsTableSize);
+ pw.print(" entries)");
+ pw.println(":");
+ dumpProcessState(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
+ ALL_PROC_STATES, now);
+ dumpProcessPss(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
+ ALL_PROC_STATES);
+ dumpProcessInternalLocked(pw, " ", proc, dumpAll);
+ }
+ } else {
+ ArrayList<ProcessState> procs = new ArrayList<ProcessState>();
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ if (!pkgMatch && !reqPackage.equals(proc.mName)) {
+ continue;
+ }
+ if (activeOnly && !proc.isInUse()) {
+ continue;
+ }
+ procs.add(proc);
+ }
+ dumpProcessSummaryLocked(pw, " ", procs, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
+ NON_CACHED_PROC_STATES, false, now, totalTime);
}
- pw.print(" * "); pw.print(pkgName); pw.print(" / ");
- UserHandle.formatUid(pw, uid); pw.println(":");
- }
- if (!dumpSummary || dumpAll) {
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- if (!pkgMatch && !reqPackage.equals(proc.mName)) {
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ ServiceState svc = pkgState.mServices.valueAt(isvc);
+ if (!pkgMatch && !reqPackage.equals(svc.mProcessName)) {
continue;
}
- if (activeOnly && !proc.isInUse()) {
+ if (activeOnly && !svc.isInUse()) {
pw.print(" (Not active: ");
- pw.print(pkgState.mProcesses.keyAt(iproc)); pw.println(")");
+ pw.print(pkgState.mServices.keyAt(isvc)); pw.println(")");
continue;
}
- pw.print(" Process ");
- pw.print(pkgState.mProcesses.keyAt(iproc));
- if (proc.mCommonProcess.mMultiPackage) {
- pw.print(" (multi, ");
+ if (dumpAll) {
+ pw.print(" Service ");
} else {
- pw.print(" (unique, ");
+ pw.print(" * ");
}
- pw.print(proc.mDurationsTableSize);
- pw.print(" entries)");
+ pw.print(pkgState.mServices.keyAt(isvc));
pw.println(":");
- dumpProcessState(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
- ALL_PROC_STATES, now);
- dumpProcessPss(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
- ALL_PROC_STATES);
- dumpProcessInternalLocked(pw, " ", proc, dumpAll);
- }
- } else {
- ArrayList<ProcessState> procs = new ArrayList<ProcessState>();
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- if (!pkgMatch && !reqPackage.equals(proc.mName)) {
- continue;
- }
- if (activeOnly && !proc.isInUse()) {
- continue;
- }
- procs.add(proc);
- }
- dumpProcessSummaryLocked(pw, " ", procs, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
- NON_CACHED_PROC_STATES, now, totalTime);
- }
- for (int isvc=0; isvc<NSRVS; isvc++) {
- ServiceState svc = pkgState.mServices.valueAt(isvc);
- if (!pkgMatch && !reqPackage.equals(svc.mProcessName)) {
- continue;
- }
- if (activeOnly && !svc.isInUse()) {
- pw.print(" (Not active: ");
- pw.print(pkgState.mServices.keyAt(isvc)); pw.println(")");
- continue;
- }
- if (dumpAll) {
- pw.print(" Service ");
- } else {
- pw.print(" * ");
- }
- pw.print(pkgState.mServices.keyAt(isvc));
- pw.println(":");
- pw.print(" Process: "); pw.println(svc.mProcessName);
- dumpServiceStats(pw, " ", " ", " ", "Running", svc,
- svc.mRunCount, ServiceState.SERVICE_RUN, svc.mRunState,
- svc.mRunStartTime, now, totalTime, !dumpSummary || dumpAll);
- dumpServiceStats(pw, " ", " ", " ", "Started", svc,
- svc.mStartedCount, ServiceState.SERVICE_STARTED, svc.mStartedState,
- svc.mStartedStartTime, now, totalTime, !dumpSummary || dumpAll);
- dumpServiceStats(pw, " ", " ", " ", "Bound", svc,
- svc.mBoundCount, ServiceState.SERVICE_BOUND, svc.mBoundState,
- svc.mBoundStartTime, now, totalTime, !dumpSummary || dumpAll);
- dumpServiceStats(pw, " ", " ", " ", "Executing", svc,
- svc.mExecCount, ServiceState.SERVICE_EXEC, svc.mExecState,
- svc.mExecStartTime, now, totalTime, !dumpSummary || dumpAll);
- if (dumpAll) {
- if (svc.mOwner != null) {
- pw.print(" mOwner="); pw.println(svc.mOwner);
- }
- if (svc.mStarted || svc.mRestarting) {
- pw.print(" mStarted="); pw.print(svc.mStarted);
- pw.print(" mRestarting="); pw.println(svc.mRestarting);
+ pw.print(" Process: "); pw.println(svc.mProcessName);
+ dumpServiceStats(pw, " ", " ", " ", "Running", svc,
+ svc.mRunCount, ServiceState.SERVICE_RUN, svc.mRunState,
+ svc.mRunStartTime, now, totalTime, !dumpSummary || dumpAll);
+ dumpServiceStats(pw, " ", " ", " ", "Started", svc,
+ svc.mStartedCount, ServiceState.SERVICE_STARTED, svc.mStartedState,
+ svc.mStartedStartTime, now, totalTime, !dumpSummary || dumpAll);
+ dumpServiceStats(pw, " ", " ", " ", "Bound", svc,
+ svc.mBoundCount, ServiceState.SERVICE_BOUND, svc.mBoundState,
+ svc.mBoundStartTime, now, totalTime, !dumpSummary || dumpAll);
+ dumpServiceStats(pw, " ", " ", " ", "Executing", svc,
+ svc.mExecCount, ServiceState.SERVICE_EXEC, svc.mExecState,
+ svc.mExecStartTime, now, totalTime, !dumpSummary || dumpAll);
+ if (dumpAll) {
+ if (svc.mOwner != null) {
+ pw.print(" mOwner="); pw.println(svc.mOwner);
+ }
+ if (svc.mStarted || svc.mRestarting) {
+ pw.print(" mStarted="); pw.print(svc.mStarted);
+ pw.print(" mRestarting="); pw.println(svc.mRestarting);
+ }
}
}
}
@@ -2059,7 +2141,7 @@ public final class ProcessStats implements Parcelable {
pw.println(header);
}
dumpProcessSummaryLocked(pw, prefix, procs, screenStates, memStates,
- sortProcStates, now, totalTime);
+ sortProcStates, true, now, totalTime);
}
}
@@ -2067,23 +2149,27 @@ public final class ProcessStats implements Parcelable {
int[] procStates, int sortProcStates[], long now, String reqPackage,
boolean activeOnly) {
final ArraySet<ProcessState> foundProcs = new ArraySet<ProcessState>();
- final ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
+ final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
for (int ip=0; ip<pkgMap.size(); ip++) {
final String pkgName = pkgMap.keyAt(ip);
- final SparseArray<PackageState> procs = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> procs = pkgMap.valueAt(ip);
for (int iu=0; iu<procs.size(); iu++) {
- final PackageState state = procs.valueAt(iu);
- final int NPROCS = state.mProcesses.size();
- final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
- for (int iproc=0; iproc<NPROCS; iproc++) {
- final ProcessState proc = state.mProcesses.valueAt(iproc);
- if (!pkgMatch && !reqPackage.equals(proc.mName)) {
- continue;
- }
- if (activeOnly && !proc.isInUse()) {
- continue;
+ final SparseArray<PackageState> vpkgs = procs.valueAt(iu);
+ final int NVERS = vpkgs.size();
+ for (int iv=0; iv<NVERS; iv++) {
+ final PackageState state = vpkgs.valueAt(iv);
+ final int NPROCS = state.mProcesses.size();
+ final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ final ProcessState proc = state.mProcesses.valueAt(iproc);
+ if (!pkgMatch && !reqPackage.equals(proc.mName)) {
+ continue;
+ }
+ if (activeOnly && !proc.isInUse()) {
+ continue;
+ }
+ foundProcs.add(proc.mCommonProcess);
}
- foundProcs.add(proc.mCommonProcess);
}
}
}
@@ -2128,8 +2214,8 @@ public final class ProcessStats implements Parcelable {
public void dumpCheckinLocked(PrintWriter pw, String reqPackage) {
final long now = SystemClock.uptimeMillis();
- ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
- pw.println("vers,3");
+ final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
+ pw.println("vers,4");
pw.print("period,"); pw.print(mTimePeriodStartClockStr);
pw.print(","); pw.print(mTimePeriodStartRealtime); pw.print(",");
pw.print(mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime);
@@ -2152,75 +2238,85 @@ public final class ProcessStats implements Parcelable {
pw.println();
pw.print("config,"); pw.print(mRuntime); pw.print(','); pw.println(mWebView);
for (int ip=0; ip<pkgMap.size(); ip++) {
- String pkgName = pkgMap.keyAt(ip);
+ final String pkgName = pkgMap.keyAt(ip);
if (reqPackage != null && !reqPackage.equals(pkgName)) {
continue;
}
- SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
for (int iu=0; iu<uids.size(); iu++) {
- int uid = uids.keyAt(iu);
- PackageState pkgState = uids.valueAt(iu);
- final int NPROCS = pkgState.mProcesses.size();
- final int NSRVS = pkgState.mServices.size();
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- pw.print("pkgproc,");
- pw.print(pkgName);
- pw.print(",");
- pw.print(uid);
- pw.print(",");
- pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
- dumpAllProcessStateCheckin(pw, proc, now);
- pw.println();
- if (proc.mPssTableSize > 0) {
- pw.print("pkgpss,");
+ final int uid = uids.keyAt(iu);
+ final SparseArray<PackageState> vpkgs = uids.valueAt(iu);
+ for (int iv=0; iv<vpkgs.size(); iv++) {
+ final int vers = vpkgs.keyAt(iv);
+ final PackageState pkgState = vpkgs.valueAt(iv);
+ final int NPROCS = pkgState.mProcesses.size();
+ final int NSRVS = pkgState.mServices.size();
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ pw.print("pkgproc,");
pw.print(pkgName);
pw.print(",");
pw.print(uid);
pw.print(",");
- pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
- dumpAllProcessPssCheckin(pw, proc);
- pw.println();
- }
- if (proc.mNumExcessiveWake > 0 || proc.mNumExcessiveCpu > 0
- || proc.mNumCachedKill > 0) {
- pw.print("pkgkills,");
- pw.print(pkgName);
- pw.print(",");
- pw.print(uid);
+ pw.print(vers);
pw.print(",");
pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
- pw.print(",");
- pw.print(proc.mNumExcessiveWake);
- pw.print(",");
- pw.print(proc.mNumExcessiveCpu);
- pw.print(",");
- pw.print(proc.mNumCachedKill);
- pw.print(",");
- pw.print(proc.mMinCachedKillPss);
- pw.print(":");
- pw.print(proc.mAvgCachedKillPss);
- pw.print(":");
- pw.print(proc.mMaxCachedKillPss);
+ dumpAllProcessStateCheckin(pw, proc, now);
pw.println();
+ if (proc.mPssTableSize > 0) {
+ pw.print("pkgpss,");
+ pw.print(pkgName);
+ pw.print(",");
+ pw.print(uid);
+ pw.print(",");
+ pw.print(vers);
+ pw.print(",");
+ pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
+ dumpAllProcessPssCheckin(pw, proc);
+ pw.println();
+ }
+ if (proc.mNumExcessiveWake > 0 || proc.mNumExcessiveCpu > 0
+ || proc.mNumCachedKill > 0) {
+ pw.print("pkgkills,");
+ pw.print(pkgName);
+ pw.print(",");
+ pw.print(uid);
+ pw.print(",");
+ pw.print(vers);
+ pw.print(",");
+ pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
+ pw.print(",");
+ pw.print(proc.mNumExcessiveWake);
+ pw.print(",");
+ pw.print(proc.mNumExcessiveCpu);
+ pw.print(",");
+ pw.print(proc.mNumCachedKill);
+ pw.print(",");
+ pw.print(proc.mMinCachedKillPss);
+ pw.print(":");
+ pw.print(proc.mAvgCachedKillPss);
+ pw.print(":");
+ pw.print(proc.mMaxCachedKillPss);
+ pw.println();
+ }
+ }
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ String serviceName = collapseString(pkgName,
+ pkgState.mServices.keyAt(isvc));
+ ServiceState svc = pkgState.mServices.valueAt(isvc);
+ dumpServiceTimeCheckin(pw, "pkgsvc-run", pkgName, uid, vers, serviceName,
+ svc, ServiceState.SERVICE_RUN, svc.mRunCount,
+ svc.mRunState, svc.mRunStartTime, now);
+ dumpServiceTimeCheckin(pw, "pkgsvc-start", pkgName, uid, vers, serviceName,
+ svc, ServiceState.SERVICE_STARTED, svc.mStartedCount,
+ svc.mStartedState, svc.mStartedStartTime, now);
+ dumpServiceTimeCheckin(pw, "pkgsvc-bound", pkgName, uid, vers, serviceName,
+ svc, ServiceState.SERVICE_BOUND, svc.mBoundCount,
+ svc.mBoundState, svc.mBoundStartTime, now);
+ dumpServiceTimeCheckin(pw, "pkgsvc-exec", pkgName, uid, vers, serviceName,
+ svc, ServiceState.SERVICE_EXEC, svc.mExecCount,
+ svc.mExecState, svc.mExecStartTime, now);
}
- }
- for (int isvc=0; isvc<NSRVS; isvc++) {
- String serviceName = collapseString(pkgName,
- pkgState.mServices.keyAt(isvc));
- ServiceState svc = pkgState.mServices.valueAt(isvc);
- dumpServiceTimeCheckin(pw, "pkgsvc-run", pkgName, uid, serviceName,
- svc, ServiceState.SERVICE_RUN, svc.mRunCount,
- svc.mRunState, svc.mRunStartTime, now);
- dumpServiceTimeCheckin(pw, "pkgsvc-start", pkgName, uid, serviceName,
- svc, ServiceState.SERVICE_STARTED, svc.mStartedCount,
- svc.mStartedState, svc.mStartedStartTime, now);
- dumpServiceTimeCheckin(pw, "pkgsvc-bound", pkgName, uid, serviceName,
- svc, ServiceState.SERVICE_BOUND, svc.mBoundCount,
- svc.mBoundState, svc.mBoundStartTime, now);
- dumpServiceTimeCheckin(pw, "pkgsvc-exec", pkgName, uid, serviceName,
- svc, ServiceState.SERVICE_EXEC, svc.mExecCount,
- svc.mExecState, svc.mExecStartTime, now);
}
}
}
@@ -2364,9 +2460,10 @@ public final class ProcessStats implements Parcelable {
}
public static final class ProcessState extends DurationsTable {
- public final ProcessState mCommonProcess;
+ public ProcessState mCommonProcess;
public final String mPackage;
public final int mUid;
+ public final int mVersion;
//final long[] mDurations = new long[STATE_COUNT*ADJ_COUNT];
int mCurState = STATE_NOTHING;
@@ -2393,16 +2490,19 @@ public final class ProcessStats implements Parcelable {
boolean mDead;
public long mTmpTotalTime;
+ int mTmpNumInUse;
+ ProcessState mTmpFoundSubProc;
/**
* Create a new top-level process state, for the initial case where there is only
* a single package running in a process. The initial state is not running.
*/
- public ProcessState(ProcessStats processStats, String pkg, int uid, String name) {
+ public ProcessState(ProcessStats processStats, String pkg, int uid, int vers, String name) {
super(processStats, name);
mCommonProcess = this;
mPackage = pkg;
mUid = uid;
+ mVersion = vers;
}
/**
@@ -2410,18 +2510,19 @@ public final class ProcessStats implements Parcelable {
* state. The current running state of the top-level process is also copied,
* marked as started running at 'now'.
*/
- public ProcessState(ProcessState commonProcess, String pkg, int uid, String name,
+ public ProcessState(ProcessState commonProcess, String pkg, int uid, int vers, String name,
long now) {
super(commonProcess.mStats, name);
mCommonProcess = commonProcess;
mPackage = pkg;
mUid = uid;
+ mVersion = vers;
mCurState = commonProcess.mCurState;
mStartTime = now;
}
ProcessState clone(String pkg, long now) {
- ProcessState pnew = new ProcessState(this, pkg, mUid, mName, now);
+ ProcessState pnew = new ProcessState(this, pkg, mUid, mVersion, mName, now);
copyDurationsTo(pnew);
if (mPssTable != null) {
mStats.mAddLongTable = new int[mPssTable.length];
@@ -2811,9 +2912,20 @@ public final class ProcessStats implements Parcelable {
// The array map is still pointing to a common process state
// that is now shared across packages. Update it to point to
// the new per-package state.
- ProcessState proc = mStats.mPackages.get(pkgName, mUid).mProcesses.get(mName);
+ SparseArray<PackageState> vpkg = mStats.mPackages.get(pkgName, mUid);
+ if (vpkg == null) {
+ throw new IllegalStateException("Didn't find package " + pkgName
+ + " / " + mUid);
+ }
+ PackageState pkg = vpkg.get(mVersion);
+ if (pkg == null) {
+ throw new IllegalStateException("Didn't find package " + pkgName
+ + " / " + mUid + " vers " + mVersion);
+ }
+ ProcessState proc = pkg.mProcesses.get(mName);
if (proc == null) {
- throw new IllegalStateException("Didn't create per-package process");
+ throw new IllegalStateException("Didn't create per-package process "
+ + mName + " in pkg " + pkgName + " / " + mUid + " vers " + mVersion);
}
return proc;
}
@@ -2829,18 +2941,26 @@ public final class ProcessStats implements Parcelable {
// are losing whatever data we had in the old process state.
Log.wtf(TAG, "Pulling dead proc: name=" + mName + " pkg=" + mPackage
+ " uid=" + mUid + " common.name=" + mCommonProcess.mName);
- proc = mStats.getProcessStateLocked(proc.mPackage, proc.mUid, proc.mName);
+ proc = mStats.getProcessStateLocked(proc.mPackage, proc.mUid, proc.mVersion,
+ proc.mName);
}
if (proc.mMultiPackage) {
// The array map is still pointing to a common process state
// that is now shared across packages. Update it to point to
// the new per-package state.
- PackageState pkg = mStats.mPackages.get(pkgList.keyAt(index), proc.mUid);
- if (pkg == null) {
+ SparseArray<PackageState> vpkg = mStats.mPackages.get(pkgList.keyAt(index),
+ proc.mUid);
+ if (vpkg == null) {
throw new IllegalStateException("No existing package "
+ pkgList.keyAt(index) + "/" + proc.mUid
+ " for multi-proc " + proc.mName);
}
+ PackageState pkg = vpkg.get(proc.mVersion);
+ if (pkg == null) {
+ throw new IllegalStateException("No existing package "
+ + pkgList.keyAt(index) + "/" + proc.mUid
+ + " for multi-proc " + proc.mName + " version " + proc.mVersion);
+ }
proc = pkg.mProcesses.get(proc.mName);
if (proc == null) {
throw new IllegalStateException("Didn't create per-package process "
@@ -3014,7 +3134,7 @@ public final class ProcessStats implements Parcelable {
}
public boolean isInUse() {
- return mOwner != null;
+ return mOwner != null || mRestarting;
}
void add(ServiceState other) {
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
new file mode 100644
index 0000000..afb6f7c
--- /dev/null
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -0,0 +1,447 @@
+/*
+ * 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.internal.app;
+
+import android.annotation.Nullable;
+import android.app.ActionBar;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.drawable.Drawable;
+import android.view.ActionMode;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.SpinnerAdapter;
+import android.widget.Toolbar;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+public class ToolbarActionBar extends ActionBar {
+ private Toolbar mToolbar;
+ private View mCustomView;
+
+ private int mDisplayOptions;
+
+ private int mNavResId;
+ private int mIconResId;
+ private int mLogoResId;
+ private Drawable mNavDrawable;
+ private Drawable mIconDrawable;
+ private Drawable mLogoDrawable;
+ private int mTitleResId;
+ private int mSubtitleResId;
+ private CharSequence mTitle;
+ private CharSequence mSubtitle;
+
+ private boolean mLastMenuVisibility;
+ private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners =
+ new ArrayList<OnMenuVisibilityListener>();
+
+ public ToolbarActionBar(Toolbar toolbar) {
+ mToolbar = toolbar;
+ }
+
+ @Override
+ public void setCustomView(View view) {
+ setCustomView(view, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ }
+
+ @Override
+ public void setCustomView(View view, LayoutParams layoutParams) {
+ if (mCustomView != null) {
+ mToolbar.removeView(mCustomView);
+ }
+ mCustomView = view;
+ if (view != null) {
+ mToolbar.addView(view, generateLayoutParams(layoutParams));
+ }
+ }
+
+ private Toolbar.LayoutParams generateLayoutParams(LayoutParams lp) {
+ final Toolbar.LayoutParams result = new Toolbar.LayoutParams(lp);
+ result.gravity = lp.gravity;
+ return result;
+ }
+
+ @Override
+ public void setCustomView(int resId) {
+ final LayoutInflater inflater = LayoutInflater.from(mToolbar.getContext());
+ setCustomView(inflater.inflate(resId, mToolbar, false));
+ }
+
+ @Override
+ public void setIcon(int resId) {
+ mIconResId = resId;
+ mIconDrawable = null;
+ updateToolbarLogo();
+ }
+
+ @Override
+ public void setIcon(Drawable icon) {
+ mIconResId = 0;
+ mIconDrawable = icon;
+ updateToolbarLogo();
+ }
+
+ @Override
+ public void setLogo(int resId) {
+ mLogoResId = resId;
+ mLogoDrawable = null;
+ updateToolbarLogo();
+ }
+
+ @Override
+ public void setLogo(Drawable logo) {
+ mLogoResId = 0;
+ mLogoDrawable = logo;
+ updateToolbarLogo();
+ }
+
+ private void updateToolbarLogo() {
+ Drawable drawable = null;
+ if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0) {
+ final int resId;
+ if ((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) {
+ resId = mLogoResId;
+ drawable = mLogoDrawable;
+ } else {
+ resId = mIconResId;
+ drawable = mIconDrawable;
+ }
+ if (resId != 0) {
+ drawable = mToolbar.getContext().getDrawable(resId);
+ }
+ }
+ mToolbar.setLogo(drawable);
+ }
+
+ @Override
+ public void setStackedBackgroundDrawable(Drawable d) {
+ // This space for rent (do nothing)
+ }
+
+ @Override
+ public void setSplitBackgroundDrawable(Drawable d) {
+ // This space for rent (do nothing)
+ }
+
+ @Override
+ public void setHomeButtonEnabled(boolean enabled) {
+ // If the nav button on a Toolbar is present, it's enabled. No-op.
+ }
+
+ @Override
+ public Context getThemedContext() {
+ return mToolbar.getContext();
+ }
+
+ @Override
+ public boolean isTitleTruncated() {
+ return super.isTitleTruncated();
+ }
+
+ @Override
+ public void setHomeAsUpIndicator(Drawable indicator) {
+ mToolbar.setNavigationIcon(indicator);
+ }
+
+ @Override
+ public void setHomeAsUpIndicator(int resId) {
+ mToolbar.setNavigationIcon(resId);
+ }
+
+ @Override
+ public void setHomeActionContentDescription(CharSequence description) {
+ mToolbar.setNavigationDescription(description);
+ }
+
+ @Override
+ public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) {
+ // Do nothing
+ }
+
+ @Override
+ public void setHomeActionContentDescription(int resId) {
+ mToolbar.setNavigationDescription(resId);
+ }
+
+ @Override
+ public void setShowHideAnimationEnabled(boolean enabled) {
+ // This space for rent; no-op.
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration config) {
+ super.onConfigurationChanged(config);
+ }
+
+ @Override
+ public ActionMode startActionMode(ActionMode.Callback callback) {
+ return mToolbar.startActionMode(callback);
+ }
+
+ @Override
+ public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) {
+ throw new UnsupportedOperationException(
+ "Navigation modes are not supported in toolbar action bars");
+ }
+
+ @Override
+ public void setSelectedNavigationItem(int position) {
+ throw new UnsupportedOperationException(
+ "Navigation modes are not supported in toolbar action bars");
+ }
+
+ @Override
+ public int getSelectedNavigationIndex() {
+ return -1;
+ }
+
+ @Override
+ public int getNavigationItemCount() {
+ return 0;
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ mTitle = title;
+ mTitleResId = 0;
+ updateToolbarTitle();
+ }
+
+ @Override
+ public void setTitle(int resId) {
+ mTitleResId = resId;
+ mTitle = null;
+ updateToolbarTitle();
+ }
+
+ @Override
+ public void setSubtitle(CharSequence subtitle) {
+ mSubtitle = subtitle;
+ mSubtitleResId = 0;
+ updateToolbarTitle();
+ }
+
+ @Override
+ public void setSubtitle(int resId) {
+ mSubtitleResId = resId;
+ mSubtitle = null;
+ updateToolbarTitle();
+ }
+
+ private void updateToolbarTitle() {
+ final Context context = mToolbar.getContext();
+ CharSequence title = null;
+ CharSequence subtitle = null;
+ if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
+ title = mTitleResId != 0 ? context.getText(mTitleResId) : mTitle;
+ subtitle = mSubtitleResId != 0 ? context.getText(mSubtitleResId) : mSubtitle;
+ }
+ mToolbar.setTitle(title);
+ mToolbar.setSubtitle(subtitle);
+ }
+
+ @Override
+ public void setDisplayOptions(@DisplayOptions int options) {
+ setDisplayOptions(options, 0xffffffff);
+ }
+
+ @Override
+ public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) {
+ final int oldOptions = mDisplayOptions;
+ mDisplayOptions = (options & mask) | (mDisplayOptions & ~mask);
+ final int optionsChanged = oldOptions ^ mDisplayOptions;
+ }
+
+ @Override
+ public void setDisplayUseLogoEnabled(boolean useLogo) {
+ setDisplayOptions(useLogo ? DISPLAY_USE_LOGO : 0, DISPLAY_USE_LOGO);
+ }
+
+ @Override
+ public void setDisplayShowHomeEnabled(boolean showHome) {
+ setDisplayOptions(showHome ? DISPLAY_SHOW_HOME : 0, DISPLAY_SHOW_HOME);
+ }
+
+ @Override
+ public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) {
+ setDisplayOptions(showHomeAsUp ? DISPLAY_HOME_AS_UP : 0, DISPLAY_HOME_AS_UP);
+ }
+
+ @Override
+ public void setDisplayShowTitleEnabled(boolean showTitle) {
+ setDisplayOptions(showTitle ? DISPLAY_SHOW_TITLE : 0, DISPLAY_SHOW_TITLE);
+ }
+
+ @Override
+ public void setDisplayShowCustomEnabled(boolean showCustom) {
+ setDisplayOptions(showCustom ? DISPLAY_SHOW_CUSTOM : 0, DISPLAY_SHOW_CUSTOM);
+ }
+
+ @Override
+ public void setBackgroundDrawable(@Nullable Drawable d) {
+ mToolbar.setBackground(d);
+ }
+
+ @Override
+ public View getCustomView() {
+ return mCustomView;
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return mToolbar.getTitle();
+ }
+
+ @Override
+ public CharSequence getSubtitle() {
+ return mToolbar.getSubtitle();
+ }
+
+ @Override
+ public int getNavigationMode() {
+ return NAVIGATION_MODE_STANDARD;
+ }
+
+ @Override
+ public void setNavigationMode(@NavigationMode int mode) {
+ throw new UnsupportedOperationException(
+ "Navigation modes are not supported in toolbar action bars");
+ }
+
+ @Override
+ public int getDisplayOptions() {
+ return mDisplayOptions;
+ }
+
+ @Override
+ public Tab newTab() {
+ throw new UnsupportedOperationException(
+ "Navigation modes are not supported in toolbar action bars");
+ }
+
+ @Override
+ public void addTab(Tab tab) {
+ throw new UnsupportedOperationException(
+ "Navigation modes are not supported in toolbar action bars");
+ }
+
+ @Override
+ public void addTab(Tab tab, boolean setSelected) {
+ throw new UnsupportedOperationException(
+ "Navigation modes are not supported in toolbar action bars");
+ }
+
+ @Override
+ public void addTab(Tab tab, int position) {
+ throw new UnsupportedOperationException(
+ "Navigation modes are not supported in toolbar action bars");
+ }
+
+ @Override
+ public void addTab(Tab tab, int position, boolean setSelected) {
+ throw new UnsupportedOperationException(
+ "Navigation modes are not supported in toolbar action bars");
+ }
+
+ @Override
+ public void removeTab(Tab tab) {
+ throw new UnsupportedOperationException(
+ "Navigation modes are not supported in toolbar action bars");
+ }
+
+ @Override
+ public void removeTabAt(int position) {
+ throw new UnsupportedOperationException(
+ "Navigation modes are not supported in toolbar action bars");
+ }
+
+ @Override
+ public void removeAllTabs() {
+ throw new UnsupportedOperationException(
+ "Navigation modes are not supported in toolbar action bars");
+ }
+
+ @Override
+ public void selectTab(Tab tab) {
+ throw new UnsupportedOperationException(
+ "Navigation modes are not supported in toolbar action bars");
+ }
+
+ @Override
+ public Tab getSelectedTab() {
+ throw new UnsupportedOperationException(
+ "Navigation modes are not supported in toolbar action bars");
+ }
+
+ @Override
+ public Tab getTabAt(int index) {
+ throw new UnsupportedOperationException(
+ "Navigation modes are not supported in toolbar action bars");
+ }
+
+ @Override
+ public int getTabCount() {
+ return 0;
+ }
+
+ @Override
+ public int getHeight() {
+ return mToolbar.getHeight();
+ }
+
+ @Override
+ public void show() {
+ // TODO: Consider a better transition for this.
+ // Right now use no automatic transition so that the app can supply one if desired.
+ mToolbar.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void hide() {
+ // TODO: Consider a better transition for this.
+ // Right now use no automatic transition so that the app can supply one if desired.
+ mToolbar.setVisibility(View.GONE);
+ }
+
+ @Override
+ public boolean isShowing() {
+ return mToolbar.getVisibility() == View.VISIBLE;
+ }
+
+ public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
+ mMenuVisibilityListeners.add(listener);
+ }
+
+ public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
+ mMenuVisibilityListeners.remove(listener);
+ }
+
+ public void dispatchMenuVisibilityChanged(boolean isVisible) {
+ if (isVisible == mLastMenuVisibility) {
+ return;
+ }
+ mLastMenuVisibility = isVisible;
+
+ final int count = mMenuVisibilityListeners.size();
+ for (int i = 0; i < count; i++) {
+ mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index ad45894..66548f0 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -17,7 +17,9 @@
package com.android.internal.app;
import android.animation.ValueAnimator;
+import android.content.res.TypedArray;
import android.view.ViewParent;
+import com.android.internal.R;
import com.android.internal.view.ActionBarPolicy;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuPopupHelper;
@@ -41,8 +43,6 @@ import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.util.Log;
import android.util.TypedValue;
import android.view.ActionMode;
import android.view.ContextThemeWrapper;
@@ -51,7 +51,6 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.view.ViewGroup;
import android.view.Window;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AnimationUtils;
@@ -61,14 +60,15 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
- * ActionBarImpl is the ActionBar implementation used
- * by devices of all screen sizes. If it detects a compatible decor,
- * it will split contextual modes across both the ActionBarView at
- * the top of the screen and a horizontal LinearLayout at the bottom
- * which is normally hidden.
+ * WindowDecorActionBar is the ActionBar implementation used
+ * by devices of all screen sizes as part of the window decor layout.
+ * If it detects a compatible decor, it will split contextual modes
+ * across both the ActionBarView at the top of the screen and
+ * a horizontal LinearLayout at the bottom which is normally hidden.
*/
-public class ActionBarImpl extends ActionBar {
- private static final String TAG = "ActionBarImpl";
+public class WindowDecorActionBar extends ActionBar implements
+ ActionBarOverlayLayout.ActionBarVisibilityCallback {
+ private static final String TAG = "WindowDecorActionBar";
private Context mContext;
private Context mThemedContext;
@@ -106,9 +106,6 @@ public class ActionBarImpl extends ActionBar {
private int mContextDisplayMode;
private boolean mHasEmbeddedTabs;
- final Handler mHandler = new Handler();
- Runnable mTabSelector;
-
private int mCurWindowVisibility = View.VISIBLE;
private boolean mContentAnimations = true;
@@ -120,6 +117,7 @@ public class ActionBarImpl extends ActionBar {
private Animator mCurrentShowAnim;
private boolean mShowHideAnimationEnabled;
+ boolean mHideOnContentScroll;
final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
@Override
@@ -136,7 +134,7 @@ public class ActionBarImpl extends ActionBar {
mCurrentShowAnim = null;
completeDeferredDestroyActionMode();
if (mOverlayLayout != null) {
- mOverlayLayout.requestFitSystemWindows();
+ mOverlayLayout.requestApplyInsets();
}
}
};
@@ -158,7 +156,7 @@ public class ActionBarImpl extends ActionBar {
}
};
- public ActionBarImpl(Activity activity) {
+ public WindowDecorActionBar(Activity activity) {
mActivity = activity;
Window window = activity.getWindow();
View decor = window.getDecorView();
@@ -169,7 +167,7 @@ public class ActionBarImpl extends ActionBar {
}
}
- public ActionBarImpl(Dialog dialog) {
+ public WindowDecorActionBar(Dialog dialog) {
mDialog = dialog;
init(dialog.getWindow().getDecorView());
}
@@ -178,17 +176,16 @@ public class ActionBarImpl extends ActionBar {
* Only for edit mode.
* @hide
*/
- public ActionBarImpl(View layout) {
+ public WindowDecorActionBar(View layout) {
assert layout.isInEditMode();
init(layout);
}
private void init(View decor) {
- mContext = decor.getContext();
mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(
com.android.internal.R.id.action_bar_overlay_layout);
if (mOverlayLayout != null) {
- mOverlayLayout.setActionBar(this);
+ mOverlayLayout.setActionBarVisibilityCallback(this);
}
mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar);
mContextView = (ActionBarContextView) decor.findViewById(
@@ -203,6 +200,7 @@ public class ActionBarImpl extends ActionBar {
"with a compatible window decor layout");
}
+ mContext = mActionView.getContext();
mActionView.setContextView(mContextView);
mContextDisplayMode = mActionView.isSplitActionBar() ?
CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL;
@@ -217,6 +215,14 @@ public class ActionBarImpl extends ActionBar {
ActionBarPolicy abp = ActionBarPolicy.get(mContext);
setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp);
setHasEmbeddedTabs(abp.hasEmbeddedTabs());
+
+ final TypedArray a = mContext.obtainStyledAttributes(null,
+ com.android.internal.R.styleable.ActionBar,
+ com.android.internal.R.attr.actionBarStyle, 0);
+ if (a.getBoolean(R.styleable.ActionBar_hideOnContentScroll, false)) {
+ setHideOnContentScrollEnabled(true);
+ }
+ a.recycle();
}
public void onConfigurationChanged(Configuration newConfig) {
@@ -238,17 +244,14 @@ public class ActionBarImpl extends ActionBar {
if (isInTabMode) {
mTabScrollView.setVisibility(View.VISIBLE);
if (mOverlayLayout != null) {
- mOverlayLayout.requestFitSystemWindows();
+ mOverlayLayout.requestApplyInsets();
}
} else {
mTabScrollView.setVisibility(View.GONE);
}
}
mActionView.setCollapsable(!mHasEmbeddedTabs && isInTabMode);
- }
-
- public boolean hasNonEmbeddedTabs() {
- return !mHasEmbeddedTabs && getNavigationMode() == NAVIGATION_MODE_TABS;
+ mOverlayLayout.setHasNonEmbeddedTabs(!mHasEmbeddedTabs && isInTabMode);
}
private void ensureTabsExist() {
@@ -283,7 +286,7 @@ public class ActionBarImpl extends ActionBar {
}
}
- public void setWindowVisibility(int visibility) {
+ public void onWindowVisibilityChanged(int visibility) {
mCurWindowVisibility = visibility;
}
@@ -457,6 +460,7 @@ public class ActionBarImpl extends ActionBar {
mActionMode.finish();
}
+ mOverlayLayout.setHideOnContentScrollEnabled(false);
mContextView.killMode();
ActionModeImpl mode = new ActionModeImpl(callback);
if (mode.dispatchOnCreate()) {
@@ -468,7 +472,7 @@ public class ActionBarImpl extends ActionBar {
if (mSplitView.getVisibility() != View.VISIBLE) {
mSplitView.setVisibility(View.VISIBLE);
if (mOverlayLayout != null) {
- mOverlayLayout.requestFitSystemWindows();
+ mOverlayLayout.requestApplyInsets();
}
}
}
@@ -656,6 +660,35 @@ public class ActionBarImpl extends ActionBar {
}
}
+ @Override
+ public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
+ if (hideOnContentScroll && !mOverlayLayout.isInOverlayMode()) {
+ throw new IllegalStateException("Action bar must be in overlay mode " +
+ "(Window.FEATURE_OVERLAY_ACTION_BAR) to enable hide on content scroll");
+ }
+ mHideOnContentScroll = hideOnContentScroll;
+ mOverlayLayout.setHideOnContentScrollEnabled(hideOnContentScroll);
+ }
+
+ @Override
+ public boolean isHideOnContentScrollEnabled() {
+ return mOverlayLayout.isHideOnContentScrollEnabled();
+ }
+
+ @Override
+ public int getHideOffset() {
+ return mOverlayLayout.getActionBarHideOffset();
+ }
+
+ @Override
+ public void setHideOffset(int offset) {
+ if (offset != 0 && !mOverlayLayout.isInOverlayMode()) {
+ throw new IllegalStateException("Action bar must be in overlay mode " +
+ "(Window.FEATURE_OVERLAY_ACTION_BAR) to set a non-zero hide offset");
+ }
+ mOverlayLayout.setActionBarHideOffset(offset);
+ }
+
private static boolean checkShowingFlags(boolean hiddenByApp, boolean hiddenBySystem,
boolean showingForMode) {
if (showingForMode) {
@@ -741,7 +774,7 @@ public class ActionBarImpl extends ActionBar {
mShowListener.onAnimationEnd(null);
}
if (mOverlayLayout != null) {
- mOverlayLayout.requestFitSystemWindows();
+ mOverlayLayout.requestApplyInsets();
}
}
@@ -785,11 +818,7 @@ public class ActionBarImpl extends ActionBar {
}
public boolean isShowing() {
- return mNowShowing;
- }
-
- public boolean isSystemShowing() {
- return !mHiddenBySystem;
+ return mNowShowing && getHideOffset() < getHeight();
}
void animateToMode(boolean toActionMode) {
@@ -848,6 +877,18 @@ public class ActionBarImpl extends ActionBar {
mActionView.setHomeActionContentDescription(resId);
}
+ @Override
+ public void onContentScrollStarted() {
+ if (mCurrentShowAnim != null) {
+ mCurrentShowAnim.cancel();
+ mCurrentShowAnim = null;
+ }
+ }
+
+ @Override
+ public void onContentScrollStopped() {
+ }
+
/**
* @hide
*/
@@ -898,6 +939,7 @@ public class ActionBarImpl extends ActionBar {
// Clear out the context mode views after the animation finishes
mContextView.closeMode();
mActionView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ mOverlayLayout.setHideOnContentScrollEnabled(mHideOnContentScroll);
mActionMode = null;
}
@@ -1092,7 +1134,7 @@ public class ActionBarImpl extends ActionBar {
@Override
public Tab setIcon(int resId) {
- return setIcon(mContext.getResources().getDrawable(resId));
+ return setIcon(mContext.getDrawable(resId));
}
@Override
@@ -1208,6 +1250,7 @@ public class ActionBarImpl extends ActionBar {
break;
}
mActionView.setCollapsable(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
+ mOverlayLayout.setHasNonEmbeddedTabs(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
}
@Override
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 7ddd5d2..5214dd9 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -59,6 +59,5 @@ interface IAppWidgetService {
void bindRemoteViewsService(int appWidgetId, in Intent intent, in IBinder connection, int userId);
void unbindRemoteViewsService(int appWidgetId, in Intent intent, int userId);
int[] getAppWidgetIds(in ComponentName provider, int userId);
-
}
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 494bc78..446ef55 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -23,22 +23,24 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
import android.os.SELinux;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
import android.util.Log;
import com.android.org.bouncycastle.util.encoders.Base64;
import java.io.File;
-import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
+
+import static android.system.OsConstants.*;
/**
* Backup transport for stashing stuff into a known location on disk, and
@@ -55,20 +57,24 @@ public class LocalTransport extends IBackupTransport.Stub {
private static final String TRANSPORT_DESTINATION_STRING
= "Backing up to debug-only private cache";
- // The single hardcoded restore set always has the same (nonzero!) token
- private static final long RESTORE_TOKEN = 1;
+ // The currently-active restore set always has the same (nonzero!) token
+ private static final long CURRENT_SET_TOKEN = 1;
private Context mContext;
private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
+ private File mCurrentSetDir = new File(mDataDir, Long.toString(CURRENT_SET_TOKEN));
+
private PackageInfo[] mRestorePackages = null;
private int mRestorePackage = -1; // Index into mRestorePackages
+ private File mRestoreDataDir;
+ private long mRestoreToken;
public LocalTransport(Context context) {
mContext = context;
- mDataDir.mkdirs();
- if (!SELinux.restorecon(mDataDir)) {
- Log.e(TAG, "SELinux restorecon failed for " + mDataDir);
+ mCurrentSetDir.mkdirs();
+ if (!SELinux.restorecon(mCurrentSetDir)) {
+ Log.e(TAG, "SELinux restorecon failed for " + mCurrentSetDir);
}
}
@@ -96,14 +102,23 @@ public class LocalTransport extends IBackupTransport.Stub {
public int initializeDevice() {
if (DEBUG) Log.v(TAG, "wiping all data");
- deleteContents(mDataDir);
+ deleteContents(mCurrentSetDir);
return BackupConstants.TRANSPORT_OK;
}
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
- if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
+ if (DEBUG) {
+ try {
+ StructStat ss = Os.fstat(data.getFileDescriptor());
+ Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName
+ + " size=" + ss.st_size);
+ } catch (ErrnoException e) {
+ Log.w(TAG, "Unable to stat input file in performBackup() on "
+ + packageInfo.packageName);
+ }
+ }
- File packageDir = new File(mDataDir, packageInfo.packageName);
+ File packageDir = new File(mCurrentSetDir, packageInfo.packageName);
packageDir.mkdirs();
// Each 'record' in the restore set is kept in its own file, named by
@@ -135,7 +150,16 @@ public class LocalTransport extends IBackupTransport.Stub {
buf = new byte[bufSize];
}
changeSet.readEntityData(buf, 0, dataSize);
- if (DEBUG) Log.v(TAG, " data size " + dataSize);
+ if (DEBUG) {
+ try {
+ long cur = Os.lseek(data.getFileDescriptor(), 0, SEEK_CUR);
+ Log.v(TAG, " read entity data; new pos=" + cur);
+ }
+ catch (ErrnoException e) {
+ Log.w(TAG, "Unable to stat input file in performBackup() on "
+ + packageInfo.packageName);
+ }
+ }
try {
entity.write(buf, 0, dataSize);
@@ -175,7 +199,7 @@ public class LocalTransport extends IBackupTransport.Stub {
public int clearBackupData(PackageInfo packageInfo) {
if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
- File packageDir = new File(mDataDir, packageInfo.packageName);
+ File packageDir = new File(mCurrentSetDir, packageInfo.packageName);
final File[] fileset = packageDir.listFiles();
if (fileset != null) {
for (File f : fileset) {
@@ -192,22 +216,38 @@ public class LocalTransport extends IBackupTransport.Stub {
}
// Restore handling
+ static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 };
public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
- // one hardcoded restore set
- RestoreSet set = new RestoreSet("Local disk image", "flash", RESTORE_TOKEN);
- RestoreSet[] array = { set };
- return array;
+ long[] existing = new long[POSSIBLE_SETS.length + 1];
+ int num = 0;
+
+ // see which possible non-current sets exist, then put the current set at the end
+ for (long token : POSSIBLE_SETS) {
+ if ((new File(mDataDir, Long.toString(token))).exists()) {
+ existing[num++] = token;
+ }
+ }
+ // and always the currently-active set last
+ existing[num++] = CURRENT_SET_TOKEN;
+
+ RestoreSet[] available = new RestoreSet[num];
+ for (int i = 0; i < available.length; i++) {
+ available[i] = new RestoreSet("Local disk image", "flash", existing[i]);
+ }
+ return available;
}
public long getCurrentRestoreSet() {
- // The hardcoded restore set always has the same token
- return RESTORE_TOKEN;
+ // The current restore set always has the same token
+ return CURRENT_SET_TOKEN;
}
public int startRestore(long token, PackageInfo[] packages) {
if (DEBUG) Log.v(TAG, "start restore " + token);
mRestorePackages = packages;
mRestorePackage = -1;
+ mRestoreToken = token;
+ mRestoreDataDir = new File(mDataDir, Long.toString(token));
return BackupConstants.TRANSPORT_OK;
}
@@ -215,7 +255,9 @@ public class LocalTransport extends IBackupTransport.Stub {
if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
while (++mRestorePackage < mRestorePackages.length) {
String name = mRestorePackages[mRestorePackage].packageName;
- if (new File(mDataDir, name).isDirectory()) {
+ // skip packages where we have a data dir but no actual contents
+ String[] contents = (new File(mRestoreDataDir, name)).list();
+ if (contents != null && contents.length > 0) {
if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name);
return name;
}
@@ -228,29 +270,32 @@ public class LocalTransport extends IBackupTransport.Stub {
public int getRestoreData(ParcelFileDescriptor outFd) {
if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
- File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName);
+ File packageDir = new File(mRestoreDataDir, mRestorePackages[mRestorePackage].packageName);
// The restore set is the concatenation of the individual record blobs,
- // each of which is a file in the package's directory
- File[] blobs = packageDir.listFiles();
+ // each of which is a file in the package's directory. We return the
+ // data in lexical order sorted by key, so that apps which use synthetic
+ // keys like BLOB_1, BLOB_2, etc will see the date in the most obvious
+ // order.
+ ArrayList<DecodedFilename> blobs = contentsByKey(packageDir);
if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error
- Log.e(TAG, "Error listing directory: " + packageDir);
+ Log.e(TAG, "No keys for package: " + packageDir);
return BackupConstants.TRANSPORT_ERROR;
}
// We expect at least some data if the directory exists in the first place
- if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.length + " key files");
+ if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.size() + " key files");
BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
try {
- for (File f : blobs) {
+ for (DecodedFilename keyEntry : blobs) {
+ File f = keyEntry.file;
FileInputStream in = new FileInputStream(f);
try {
int size = (int) f.length();
byte[] buf = new byte[size];
in.read(buf);
- String key = new String(Base64.decode(f.getName()));
- if (DEBUG) Log.v(TAG, " ... key=" + key + " size=" + size);
- out.writeEntityHeader(key, size);
+ if (DEBUG) Log.v(TAG, " ... key=" + keyEntry.key + " size=" + size);
+ out.writeEntityHeader(keyEntry.key, size);
out.writeEntityData(buf, size);
} finally {
in.close();
@@ -263,6 +308,39 @@ public class LocalTransport extends IBackupTransport.Stub {
}
}
+ static class DecodedFilename implements Comparable<DecodedFilename> {
+ public File file;
+ public String key;
+
+ public DecodedFilename(File f) {
+ file = f;
+ key = new String(Base64.decode(f.getName()));
+ }
+
+ @Override
+ public int compareTo(DecodedFilename other) {
+ // sorts into ascending lexical order by decoded key
+ return key.compareTo(other.key);
+ }
+ }
+
+ // Return a list of the files in the given directory, sorted lexically by
+ // the Base64-decoded file name, not by the on-disk filename
+ private ArrayList<DecodedFilename> contentsByKey(File dir) {
+ File[] allFiles = dir.listFiles();
+ if (allFiles == null || allFiles.length == 0) {
+ return null;
+ }
+
+ // Decode the filenames into keys then sort lexically by key
+ ArrayList<DecodedFilename> contents = new ArrayList<DecodedFilename>();
+ for (File f : allFiles) {
+ contents.add(new DecodedFilename(f));
+ }
+ Collections.sort(contents);
+ return contents;
+ }
+
public void finishRestore() {
if (DEBUG) Log.v(TAG, "finishRestore()");
}
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 942995b..9df8ad5 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -22,7 +22,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.Looper;
import android.os.UserHandle;
import com.android.internal.os.BackgroundThread;
@@ -243,7 +242,11 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
public boolean anyPackagesDisappearing() {
return mDisappearingPackages != null;
}
-
+
+ public boolean isReplacing() {
+ return mChangeType == PACKAGE_UPDATING;
+ }
+
public boolean isPackageModified(String packageName) {
if (mModifiedPackages != null) {
for (int i=mModifiedPackages.length-1; i>=0; i--) {
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
new file mode 100644
index 0000000..cba09d1
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.TreeMap;
+
+/**
+ * InputMethodSubtypeSwitchingController controls the switching behavior of the subtypes.
+ */
+public class InputMethodSubtypeSwitchingController {
+ private static final String TAG = InputMethodSubtypeSwitchingController.class.getSimpleName();
+ private static final boolean DEBUG = false;
+ // TODO: Turn on this flag and add CTS when the platform starts expecting that all IMEs return
+ // true for supportsSwitchingToNextInputMethod().
+ private static final boolean REQUIRE_SWITCHING_SUPPORT = false;
+ private static final int MAX_HISTORY_SIZE = 4;
+ private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
+
+ private static class SubtypeParams {
+ public final InputMethodInfo mImi;
+ public final InputMethodSubtype mSubtype;
+ public final long mTime;
+
+ public SubtypeParams(InputMethodInfo imi, InputMethodSubtype subtype) {
+ mImi = imi;
+ mSubtype = subtype;
+ mTime = System.currentTimeMillis();
+ }
+ }
+
+ public static class ImeSubtypeListItem implements Comparable<ImeSubtypeListItem> {
+ public final CharSequence mImeName;
+ public final CharSequence mSubtypeName;
+ public final InputMethodInfo mImi;
+ public final int mSubtypeId;
+ private final boolean mIsSystemLocale;
+ private final boolean mIsSystemLanguage;
+
+ public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName,
+ InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) {
+ mImeName = imeName;
+ mSubtypeName = subtypeName;
+ mImi = imi;
+ mSubtypeId = subtypeId;
+ if (TextUtils.isEmpty(subtypeLocale)) {
+ mIsSystemLocale = false;
+ mIsSystemLanguage = false;
+ } else {
+ mIsSystemLocale = subtypeLocale.equals(systemLocale);
+ mIsSystemLanguage = mIsSystemLocale
+ || subtypeLocale.startsWith(systemLocale.substring(0, 2));
+ }
+ }
+
+ @Override
+ public int compareTo(ImeSubtypeListItem other) {
+ if (TextUtils.isEmpty(mImeName)) {
+ return 1;
+ }
+ if (TextUtils.isEmpty(other.mImeName)) {
+ return -1;
+ }
+ if (!TextUtils.equals(mImeName, other.mImeName)) {
+ return mImeName.toString().compareTo(other.mImeName.toString());
+ }
+ if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) {
+ return 0;
+ }
+ if (mIsSystemLocale) {
+ return -1;
+ }
+ if (other.mIsSystemLocale) {
+ return 1;
+ }
+ if (mIsSystemLanguage) {
+ return -1;
+ }
+ if (other.mIsSystemLanguage) {
+ return 1;
+ }
+ if (TextUtils.isEmpty(mSubtypeName)) {
+ return 1;
+ }
+ if (TextUtils.isEmpty(other.mSubtypeName)) {
+ return -1;
+ }
+ return mSubtypeName.toString().compareTo(other.mSubtypeName.toString());
+ }
+ }
+
+ private static class InputMethodAndSubtypeList {
+ private final Context mContext;
+ // Used to load label
+ private final PackageManager mPm;
+ private final String mSystemLocaleStr;
+ private final InputMethodSettings mSettings;
+
+ public InputMethodAndSubtypeList(Context context, InputMethodSettings settings) {
+ mContext = context;
+ mSettings = settings;
+ mPm = context.getPackageManager();
+ final Locale locale = context.getResources().getConfiguration().locale;
+ mSystemLocaleStr = locale != null ? locale.toString() : "";
+ }
+
+ private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis =
+ new TreeMap<InputMethodInfo, List<InputMethodSubtype>>(
+ new Comparator<InputMethodInfo>() {
+ @Override
+ public int compare(InputMethodInfo imi1, InputMethodInfo imi2) {
+ if (imi2 == null)
+ return 0;
+ if (imi1 == null)
+ return 1;
+ if (mPm == null) {
+ return imi1.getId().compareTo(imi2.getId());
+ }
+ CharSequence imiId1 = imi1.loadLabel(mPm) + "/" + imi1.getId();
+ CharSequence imiId2 = imi2.loadLabel(mPm) + "/" + imi2.getId();
+ return imiId1.toString().compareTo(imiId2.toString());
+ }
+ });
+
+ public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList() {
+ return getSortedInputMethodAndSubtypeList(true, false, false);
+ }
+
+ public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(
+ boolean showSubtypes, boolean inputShown, boolean isScreenLocked) {
+ final ArrayList<ImeSubtypeListItem> imList =
+ new ArrayList<ImeSubtypeListItem>();
+ final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
+ mSettings.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(
+ mContext);
+ if (immis == null || immis.size() == 0) {
+ return Collections.emptyList();
+ }
+ mSortedImmis.clear();
+ mSortedImmis.putAll(immis);
+ for (InputMethodInfo imi : mSortedImmis.keySet()) {
+ if (imi == null) {
+ continue;
+ }
+ List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi);
+ HashSet<String> enabledSubtypeSet = new HashSet<String>();
+ for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) {
+ enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
+ }
+ final CharSequence imeLabel = imi.loadLabel(mPm);
+ if (showSubtypes && enabledSubtypeSet.size() > 0) {
+ final int subtypeCount = imi.getSubtypeCount();
+ if (DEBUG) {
+ Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
+ }
+ for (int j = 0; j < subtypeCount; ++j) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(j);
+ final String subtypeHashCode = String.valueOf(subtype.hashCode());
+ // We show all enabled IMEs and subtypes when an IME is shown.
+ if (enabledSubtypeSet.contains(subtypeHashCode)
+ && ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) {
+ final CharSequence subtypeLabel =
+ subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
+ .getDisplayName(mContext, imi.getPackageName(),
+ imi.getServiceInfo().applicationInfo);
+ imList.add(new ImeSubtypeListItem(imeLabel,
+ subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
+
+ // Removing this subtype from enabledSubtypeSet because we no
+ // longer need to add an entry of this subtype to imList to avoid
+ // duplicated entries.
+ enabledSubtypeSet.remove(subtypeHashCode);
+ }
+ }
+ } else {
+ imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
+ mSystemLocaleStr));
+ }
+ }
+ Collections.sort(imList);
+ return imList;
+ }
+ }
+
+ private final ArrayDeque<SubtypeParams> mTypedSubtypeHistory = new ArrayDeque<SubtypeParams>();
+ private final Object mLock = new Object();
+ private final InputMethodSettings mSettings;
+ private InputMethodAndSubtypeList mSubtypeList;
+
+ @VisibleForTesting
+ public static ImeSubtypeListItem getNextInputMethodImpl(List<ImeSubtypeListItem> imList,
+ boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) {
+ if (imi == null) {
+ return null;
+ }
+ if (imList.size() <= 1) {
+ return null;
+ }
+ final int N = imList.size();
+ final int currentSubtypeId =
+ subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi,
+ subtype.hashCode()) : NOT_A_SUBTYPE_ID;
+ for (int i = 0; i < N; ++i) {
+ final ImeSubtypeListItem isli = imList.get(i);
+ if (isli.mImi.equals(imi) && isli.mSubtypeId == currentSubtypeId) {
+ if (!onlyCurrentIme) {
+ return imList.get((i + 1) % N);
+ }
+ for (int j = 0; j < N - 1; ++j) {
+ final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N);
+ if (candidate.mImi.equals(imi)) {
+ return candidate;
+ }
+ }
+ return null;
+ }
+ }
+ return null;
+ }
+
+ public InputMethodSubtypeSwitchingController(InputMethodSettings settings) {
+ mSettings = settings;
+ }
+
+ // TODO: write unit tests for this method and the logic that determines the next subtype
+ public void onCommitText(InputMethodInfo imi, InputMethodSubtype subtype) {
+ synchronized (mTypedSubtypeHistory) {
+ if (subtype == null) {
+ Slog.w(TAG, "Invalid InputMethodSubtype: " + imi.getId() + ", " + subtype);
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "onCommitText: " + imi.getId() + ", " + subtype);
+ }
+ if (REQUIRE_SWITCHING_SUPPORT) {
+ if (!imi.supportsSwitchingToNextInputMethod()) {
+ Slog.w(TAG, imi.getId() + " doesn't support switching to next input method.");
+ return;
+ }
+ }
+ if (mTypedSubtypeHistory.size() >= MAX_HISTORY_SIZE) {
+ mTypedSubtypeHistory.poll();
+ }
+ mTypedSubtypeHistory.addFirst(new SubtypeParams(imi, subtype));
+ }
+ }
+
+ public void resetCircularListLocked(Context context) {
+ synchronized(mLock) {
+ mSubtypeList = new InputMethodAndSubtypeList(context, mSettings);
+ }
+ }
+
+ public ImeSubtypeListItem getNextInputMethod(
+ boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) {
+ synchronized(mLock) {
+ return getNextInputMethodImpl(mSubtypeList.getSortedInputMethodAndSubtypeList(),
+ onlyCurrentIme, imi, subtype);
+ }
+ }
+
+ public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(boolean showSubtypes,
+ boolean inputShown, boolean isScreenLocked) {
+ synchronized(mLock) {
+ return mSubtypeList.getSortedInputMethodAndSubtypeList(
+ showSubtypes, inputShown, isScreenLocked);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index 63d018f..ac3274d 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -504,6 +504,7 @@ public class InputMethodUtils {
private String mEnabledInputMethodsStrCache;
private int mCurrentUserId;
+ private int[] mCurrentProfileIds = new int[0];
private static void buildEnabledInputMethodsSettingString(
StringBuilder builder, Pair<String, ArrayList<String>> pair) {
@@ -536,6 +537,22 @@ public class InputMethodUtils {
mCurrentUserId = userId;
}
+ public void setCurrentProfileIds(int[] currentProfileIds) {
+ synchronized (this) {
+ mCurrentProfileIds = currentProfileIds;
+ }
+ }
+
+ public boolean isCurrentProfile(int userId) {
+ synchronized (this) {
+ if (userId == mCurrentUserId) return true;
+ for (int i = 0; i < mCurrentProfileIds.length; i++) {
+ if (userId == mCurrentProfileIds[i]) return true;
+ }
+ return false;
+ }
+ }
+
public List<InputMethodInfo> getEnabledInputMethodListLocked() {
return createEnabledInputMethodListLocked(
getEnabledInputMethodsAndSubtypeListLocked());
@@ -959,5 +976,16 @@ public class InputMethodUtils {
addSubtypeToHistory(curMethodId, subtypeId);
}
}
+
+ public HashMap<InputMethodInfo, List<InputMethodSubtype>>
+ getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(Context context) {
+ HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
+ new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
+ for (InputMethodInfo imi: getEnabledInputMethodListLocked()) {
+ enabledInputMethodAndSubtypes.put(
+ imi, getEnabledInputMethodSubtypeListLocked(context, imi, true));
+ }
+ return enabledInputMethodAndSubtypes;
+ }
}
}
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index 8282d23..e2a2b1e 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -17,6 +17,7 @@
package com.android.internal.net;
import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.TAG_ALL;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
@@ -26,6 +27,7 @@ import android.os.StrictMode;
import android.os.SystemClock;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ProcFileReader;
import java.io.File;
@@ -165,22 +167,32 @@ public class NetworkStatsFactory {
}
public NetworkStats readNetworkStatsDetail() throws IOException {
- return readNetworkStatsDetail(UID_ALL);
+ return readNetworkStatsDetail(UID_ALL, null, TAG_ALL, null);
}
- public NetworkStats readNetworkStatsDetail(int limitUid) throws IOException {
+ public NetworkStats readNetworkStatsDetail(int limitUid, String[] limitIfaces, int limitTag,
+ NetworkStats lastStats)
+ throws IOException {
if (USE_NATIVE_PARSING) {
- final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
- if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid) != 0) {
+ final NetworkStats stats;
+ if (lastStats != null) {
+ stats = lastStats;
+ stats.setElapsedRealtime(SystemClock.elapsedRealtime());
+ } else {
+ stats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
+ }
+ if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid,
+ limitIfaces, limitTag) != 0) {
throw new IOException("Failed to parse network stats");
}
if (SANITY_CHECK_NATIVE) {
- final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid);
+ final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid,
+ limitIfaces, limitTag);
assertEquals(javaStats, stats);
}
return stats;
} else {
- return javaReadNetworkStatsDetail(mStatsXtUid, limitUid);
+ return javaReadNetworkStatsDetail(mStatsXtUid, limitUid, limitIfaces, limitTag);
}
}
@@ -189,7 +201,8 @@ public class NetworkStatsFactory {
* expected to monotonically increase since device boot.
*/
@VisibleForTesting
- public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid)
+ public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid,
+ String[] limitIfaces, int limitTag)
throws IOException {
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
@@ -222,7 +235,9 @@ public class NetworkStatsFactory {
entry.txBytes = reader.nextLong();
entry.txPackets = reader.nextLong();
- if (limitUid == UID_ALL || limitUid == entry.uid) {
+ if ((limitIfaces == null || ArrayUtils.contains(limitIfaces, entry.iface))
+ && (limitUid == UID_ALL || limitUid == entry.uid)
+ && (limitTag == TAG_ALL || limitTag == entry.tag)) {
stats.addValues(entry);
}
@@ -264,5 +279,5 @@ public class NetworkStatsFactory {
*/
@VisibleForTesting
public static native int nativeReadNetworkStatsDetail(
- NetworkStats stats, String path, int limitUid);
+ NetworkStats stats, String path, int limitUid, String[] limitIfaces, int limitTag);
}
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index 98599d0..0d00f41 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -25,8 +25,6 @@ import android.os.UserHandle;
import android.net.RouteInfo;
import android.net.LinkAddress;
-import com.android.internal.util.Preconditions;
-
import java.net.InetAddress;
import java.util.List;
import java.util.ArrayList;
diff --git a/core/java/com/android/internal/notification/PeopleNotificationScorer.java b/core/java/com/android/internal/notification/PeopleNotificationScorer.java
new file mode 100644
index 0000000..efb5f63
--- /dev/null
+++ b/core/java/com/android/internal/notification/PeopleNotificationScorer.java
@@ -0,0 +1,227 @@
+/*
+* 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.internal.notification;
+
+import android.app.Notification;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.LruCache;
+import android.util.Slog;
+
+/**
+ * This {@link NotificationScorer} attempts to validate people references.
+ * Also elevates the priority of real people.
+ */
+public class PeopleNotificationScorer implements NotificationScorer {
+ private static final String TAG = "PeopleNotificationScorer";
+ private static final boolean DBG = false;
+
+ private static final boolean ENABLE_PEOPLE_SCORER = true;
+ private static final String SETTING_ENABLE_PEOPLE_SCORER = "people_scorer_enabled";
+ private static final String[] LOOKUP_PROJECTION = { Contacts._ID };
+ private static final int MAX_PEOPLE = 10;
+ private static final int PEOPLE_CACHE_SIZE = 200;
+ // see NotificationManagerService
+ private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10;
+
+ protected boolean mEnabled;
+ private Context mContext;
+
+ // maps raw person handle to resolved person object
+ private LruCache<String, LookupResult> mPeopleCache;
+
+ private float findMaxContactScore(Bundle extras) {
+ if (extras == null) {
+ return 0f;
+ }
+
+ final String[] people = extras.getStringArray(Notification.EXTRA_PEOPLE);
+ if (people == null || people.length == 0) {
+ return 0f;
+ }
+
+ float rank = 0f;
+ for (int personIdx = 0; personIdx < people.length && personIdx < MAX_PEOPLE; personIdx++) {
+ final String handle = people[personIdx];
+ if (TextUtils.isEmpty(handle)) continue;
+
+ LookupResult lookupResult = mPeopleCache.get(handle);
+ if (lookupResult == null || lookupResult.isExpired()) {
+ final Uri uri = Uri.parse(handle);
+ if ("tel".equals(uri.getScheme())) {
+ if (DBG) Slog.w(TAG, "checking telephone URI: " + handle);
+ lookupResult = lookupPhoneContact(handle, uri.getSchemeSpecificPart());
+ } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
+ if (DBG) Slog.w(TAG, "checking lookup URI: " + handle);
+ lookupResult = resolveContactsUri(handle, uri);
+ } else {
+ if (DBG) Slog.w(TAG, "unsupported URI " + handle);
+ }
+ } else {
+ if (DBG) Slog.w(TAG, "using cached lookupResult: " + lookupResult.mId);
+ }
+ if (lookupResult != null) {
+ rank = Math.max(rank, lookupResult.getRank());
+ }
+ }
+ return rank;
+ }
+
+ private LookupResult lookupPhoneContact(final String handle, final String number) {
+ LookupResult lookupResult = null;
+ Cursor c = null;
+ try {
+ Uri numberUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(number));
+ c = mContext.getContentResolver().query(numberUri, LOOKUP_PROJECTION, null, null, null);
+ if (c != null && c.getCount() > 0) {
+ c.moveToFirst();
+ final int idIdx = c.getColumnIndex(Contacts._ID);
+ final int id = c.getInt(idIdx);
+ if (DBG) Slog.w(TAG, "is valid: " + id);
+ lookupResult = new LookupResult(id);
+ }
+ } catch(Throwable t) {
+ Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ if (lookupResult == null) {
+ lookupResult = new LookupResult(LookupResult.INVALID_ID);
+ }
+ mPeopleCache.put(handle, lookupResult);
+ return lookupResult;
+ }
+
+ private LookupResult resolveContactsUri(String handle, final Uri personUri) {
+ LookupResult lookupResult = null;
+ Cursor c = null;
+ try {
+ c = mContext.getContentResolver().query(personUri, LOOKUP_PROJECTION, null, null, null);
+ if (c != null && c.getCount() > 0) {
+ c.moveToFirst();
+ final int idIdx = c.getColumnIndex(Contacts._ID);
+ final int id = c.getInt(idIdx);
+ if (DBG) Slog.w(TAG, "is valid: " + id);
+ lookupResult = new LookupResult(id);
+ }
+ } catch(Throwable t) {
+ Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ if (lookupResult == null) {
+ lookupResult = new LookupResult(LookupResult.INVALID_ID);
+ }
+ mPeopleCache.put(handle, lookupResult);
+ return lookupResult;
+ }
+
+ private final static int clamp(int x, int low, int high) {
+ return (x < low) ? low : ((x > high) ? high : x);
+ }
+
+ // TODO: rework this function before shipping
+ private static int priorityBumpMap(int incomingScore) {
+ //assumption is that scale runs from [-2*pm, 2*pm]
+ int pm = NOTIFICATION_PRIORITY_MULTIPLIER;
+ int theScore = incomingScore;
+ // enforce input in range
+ theScore = clamp(theScore, -2 * pm, 2 * pm);
+ if (theScore != incomingScore) return incomingScore;
+ // map -20 -> -20 and -10 -> 5 (when pm = 10)
+ if (theScore <= -pm) {
+ theScore += 1.5 * (theScore + 2 * pm);
+ } else {
+ // map 0 -> 10, 10 -> 15, 20 -> 20;
+ theScore += 0.5 * (2 * pm - theScore);
+ }
+ if (DBG) Slog.v(TAG, "priorityBumpMap: score before: " + incomingScore
+ + ", score after " + theScore + ".");
+ return theScore;
+ }
+
+ @Override
+ public void initialize(Context context) {
+ if (DBG) Slog.v(TAG, "Initializing " + getClass().getSimpleName() + ".");
+ mContext = context;
+ mPeopleCache = new LruCache<String, LookupResult>(PEOPLE_CACHE_SIZE);
+ mEnabled = ENABLE_PEOPLE_SCORER && 1 == Settings.Global.getInt(
+ mContext.getContentResolver(), SETTING_ENABLE_PEOPLE_SCORER, 0);
+ }
+
+ @Override
+ public int getScore(Notification notification, int score) {
+ if (notification == null || !mEnabled) {
+ if (DBG) Slog.w(TAG, "empty notification? scorer disabled?");
+ return score;
+ }
+ float contactScore = findMaxContactScore(notification.extras);
+ if (contactScore > 0f) {
+ if (DBG) Slog.v(TAG, "Notification references a real contact. Promoted!");
+ score = priorityBumpMap(score);
+ } else {
+ if (DBG) Slog.v(TAG, "Notification lacks any valid contact reference. Not promoted!");
+ }
+ return score;
+ }
+
+ private static class LookupResult {
+ private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000; // 1hr
+ public static final int INVALID_ID = -1;
+
+ private final long mExpireMillis;
+ private int mId;
+
+ public LookupResult(int id) {
+ mId = id;
+ mExpireMillis = System.currentTimeMillis() + CONTACT_REFRESH_MILLIS;
+ }
+
+ public boolean isExpired() {
+ return mExpireMillis < System.currentTimeMillis();
+ }
+
+ public boolean isInvalid() {
+ return mId == INVALID_ID || isExpired();
+ }
+
+ public float getRank() {
+ if (isInvalid()) {
+ return 0f;
+ } else {
+ return 1f; // TODO: finer grained score
+ }
+ }
+
+ public LookupResult setId(int id) {
+ mId = id;
+ return this;
+ }
+ }
+}
+
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
new file mode 100644
index 0000000..6ca24d7
--- /dev/null
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import android.os.BatteryStats.Uid;
+
+/**
+ * Contains power usage of an application, system service, or hardware type.
+ */
+public class BatterySipper implements Comparable<BatterySipper> {
+ public int userId;
+ public Uid uidObj;
+ public double value;
+ public double[] values;
+ public DrainType drainType;
+ public long usageTime;
+ public long cpuTime;
+ public long gpsTime;
+ public long wifiRunningTime;
+ public long cpuFgTime;
+ public long wakeLockTime;
+ public long mobileRxPackets;
+ public long mobileTxPackets;
+ public long mobileActive;
+ public int mobileActiveCount;
+ public double mobilemspp; // milliseconds per packet
+ public long wifiRxPackets;
+ public long wifiTxPackets;
+ public long mobileRxBytes;
+ public long mobileTxBytes;
+ public long wifiRxBytes;
+ public long wifiTxBytes;
+ public double percent;
+ public double noCoveragePercent;
+ public String[] mPackages;
+ public String packageWithHighestDrain;
+
+ public enum DrainType {
+ IDLE,
+ CELL,
+ PHONE,
+ WIFI,
+ BLUETOOTH,
+ SCREEN,
+ APP,
+ USER,
+ UNACCOUNTED,
+ OVERCOUNTED
+ }
+
+ public BatterySipper(DrainType drainType, Uid uid, double[] values) {
+ this.values = values;
+ if (values != null) value = values[0];
+ this.drainType = drainType;
+ uidObj = uid;
+ }
+
+ public double[] getValues() {
+ return values;
+ }
+
+ public void computeMobilemspp() {
+ long packets = mobileRxPackets+mobileTxPackets;
+ mobilemspp = packets > 0 ? (mobileActive / (double)packets) : 0;
+ }
+
+ @Override
+ public int compareTo(BatterySipper other) {
+ // Return the flipped value because we want the items in descending order
+ return Double.compare(other.value, value);
+ }
+
+ /**
+ * Gets a list of packages associated with the current user
+ */
+ public String[] getPackages() {
+ return mPackages;
+ }
+
+ public int getUid() {
+ // Bail out if the current sipper is not an App sipper.
+ if (uidObj == null) {
+ return 0;
+ }
+ return uidObj.getUid();
+ }
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
new file mode 100644
index 0000000..7ff949e
--- /dev/null
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -0,0 +1,838 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static android.os.BatteryStats.NETWORK_MOBILE_RX_DATA;
+import static android.os.BatteryStats.NETWORK_MOBILE_TX_DATA;
+import static android.os.BatteryStats.NETWORK_WIFI_RX_DATA;
+import static android.os.BatteryStats.NETWORK_WIFI_TX_DATA;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.net.ConnectivityManager;
+import android.os.BatteryStats;
+import android.os.BatteryStats.Uid;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.telephony.SignalStrength;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.os.BatterySipper.DrainType;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A helper class for retrieving the power usage information for all applications and services.
+ *
+ * The caller must initialize this class as soon as activity object is ready to use (for example, in
+ * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy().
+ */
+public class BatteryStatsHelper {
+
+ private static final boolean DEBUG = false;
+
+ private static final String TAG = BatteryStatsHelper.class.getSimpleName();
+
+ private static BatteryStats sStatsXfer;
+ private static Intent sBatteryBroadcastXfer;
+
+ final private Context mContext;
+ final private boolean mCollectBatteryBroadcast;
+
+ private IBatteryStats mBatteryInfo;
+ private BatteryStats mStats;
+ private Intent mBatteryBroadcast;
+ private PowerProfile mPowerProfile;
+
+ private final List<BatterySipper> mUsageList = new ArrayList<BatterySipper>();
+ private final List<BatterySipper> mWifiSippers = new ArrayList<BatterySipper>();
+ private final List<BatterySipper> mBluetoothSippers = new ArrayList<BatterySipper>();
+ private final SparseArray<List<BatterySipper>> mUserSippers
+ = new SparseArray<List<BatterySipper>>();
+ private final SparseArray<Double> mUserPower = new SparseArray<Double>();
+
+ private final List<BatterySipper> mMobilemsppList = new ArrayList<BatterySipper>();
+
+ private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
+ private int mAsUser = 0;
+
+ long mRawRealtime;
+ long mRawUptime;
+ long mBatteryRealtime;
+ long mBatteryUptime;
+ long mTypeBatteryRealtime;
+ long mTypeBatteryUptime;
+ long mBatteryTimeRemaining;
+ long mChargeTimeRemaining;
+
+ private long mStatsPeriod = 0;
+ private double mMaxPower = 1;
+ private double mComputedPower;
+ private double mTotalPower;
+ private double mWifiPower;
+ private double mBluetoothPower;
+ private double mMinDrainedPower;
+ private double mMaxDrainedPower;
+
+ // How much the apps together have kept the mobile radio active.
+ private long mAppMobileActive;
+
+ // How much the apps together have left WIFI running.
+ private long mAppWifiRunning;
+
+ public BatteryStatsHelper(Context context) {
+ this(context, true);
+ }
+
+ public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast) {
+ mContext = context;
+ mCollectBatteryBroadcast = collectBatteryBroadcast;
+ }
+
+ /** Clears the current stats and forces recreating for future use. */
+ public void clearStats() {
+ mStats = null;
+ }
+
+ public BatteryStats getStats() {
+ if (mStats == null) {
+ load();
+ }
+ return mStats;
+ }
+
+ public Intent getBatteryBroadcast() {
+ if (mBatteryBroadcast == null && mCollectBatteryBroadcast) {
+ load();
+ }
+ return mBatteryBroadcast;
+ }
+
+ public PowerProfile getPowerProfile() {
+ return mPowerProfile;
+ }
+
+ public void create(BatteryStats stats) {
+ mPowerProfile = new PowerProfile(mContext);
+ mStats = stats;
+ }
+
+ public void create(Bundle icicle) {
+ if (icicle != null) {
+ mStats = sStatsXfer;
+ mBatteryBroadcast = sBatteryBroadcastXfer;
+ }
+ mBatteryInfo = IBatteryStats.Stub.asInterface(
+ ServiceManager.getService(BatteryStats.SERVICE_NAME));
+ mPowerProfile = new PowerProfile(mContext);
+ }
+
+ public void storeState() {
+ sStatsXfer = mStats;
+ sBatteryBroadcastXfer = mBatteryBroadcast;
+ }
+
+ public static String makemAh(double power) {
+ if (power < .00001) return String.format("%.8f", power);
+ else if (power < .0001) return String.format("%.7f", power);
+ else if (power < .001) return String.format("%.6f", power);
+ else if (power < .01) return String.format("%.5f", power);
+ else if (power < .1) return String.format("%.4f", power);
+ else if (power < 1) return String.format("%.3f", power);
+ else if (power < 10) return String.format("%.2f", power);
+ else if (power < 100) return String.format("%.1f", power);
+ else return String.format("%.0f", power);
+ }
+
+ /**
+ * Refreshes the power usage list.
+ */
+ public void refreshStats(int statsType, int asUser) {
+ refreshStats(statsType, asUser, SystemClock.elapsedRealtime() * 1000,
+ SystemClock.uptimeMillis() * 1000);
+ }
+
+ public void refreshStats(int statsType, int asUser, long rawRealtimeUs, long rawUptimeUs) {
+ // Initialize mStats if necessary.
+ getStats();
+
+ mMaxPower = 0;
+ mComputedPower = 0;
+ mTotalPower = 0;
+ mWifiPower = 0;
+ mBluetoothPower = 0;
+ mAppMobileActive = 0;
+ mAppWifiRunning = 0;
+
+ mUsageList.clear();
+ mWifiSippers.clear();
+ mBluetoothSippers.clear();
+ mUserSippers.clear();
+ mUserPower.clear();
+ mMobilemsppList.clear();
+
+ if (mStats == null) {
+ return;
+ }
+
+ mStatsType = statsType;
+ mAsUser = asUser;
+ mRawUptime = rawUptimeUs;
+ mRawRealtime = rawRealtimeUs;
+ mBatteryUptime = mStats.getBatteryUptime(rawUptimeUs);
+ mBatteryRealtime = mStats.getBatteryRealtime(rawRealtimeUs);
+ mTypeBatteryUptime = mStats.computeBatteryUptime(rawUptimeUs, mStatsType);
+ mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType);
+ mBatteryTimeRemaining = mStats.computeBatteryTimeRemaining(rawRealtimeUs);
+ mChargeTimeRemaining = mStats.computeChargeTimeRemaining(rawRealtimeUs);
+
+ if (DEBUG) {
+ Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs/1000) + " uptime="
+ + (rawUptimeUs/1000));
+ Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtime/1000) + " uptime="
+ + (mBatteryUptime/1000));
+ Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtime/1000) + " uptime="
+ + (mTypeBatteryUptime/1000));
+ }
+ mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge()
+ * mPowerProfile.getBatteryCapacity()) / 100;
+ mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge()
+ * mPowerProfile.getBatteryCapacity()) / 100;
+
+ processAppUsage();
+
+ // Before aggregating apps in to users, collect all apps to sort by their ms per packet.
+ for (int i=0; i<mUsageList.size(); i++) {
+ BatterySipper bs = mUsageList.get(i);
+ bs.computeMobilemspp();
+ if (bs.mobilemspp != 0) {
+ mMobilemsppList.add(bs);
+ }
+ }
+ for (int i=0; i<mUserSippers.size(); i++) {
+ List<BatterySipper> user = mUserSippers.valueAt(i);
+ for (int j=0; j<user.size(); j++) {
+ BatterySipper bs = user.get(j);
+ bs.computeMobilemspp();
+ if (bs.mobilemspp != 0) {
+ mMobilemsppList.add(bs);
+ }
+ }
+ }
+ Collections.sort(mMobilemsppList, new Comparator<BatterySipper>() {
+ @Override
+ public int compare(BatterySipper lhs, BatterySipper rhs) {
+ if (lhs.mobilemspp < rhs.mobilemspp) {
+ return 1;
+ } else if (lhs.mobilemspp > rhs.mobilemspp) {
+ return -1;
+ }
+ return 0;
+ }
+ });
+
+ processMiscUsage();
+
+ if (DEBUG) {
+ Log.d(TAG, "Accuracy: total computed=" + makemAh(mComputedPower) + ", min discharge="
+ + makemAh(mMinDrainedPower) + ", max discharge=" + makemAh(mMaxDrainedPower));
+ }
+ mTotalPower = mComputedPower;
+ if (mStats.getLowDischargeAmountSinceCharge() > 1) {
+ if (mMinDrainedPower > mComputedPower) {
+ double amount = mMinDrainedPower - mComputedPower;
+ mTotalPower = mMinDrainedPower;
+ addEntryNoTotal(BatterySipper.DrainType.UNACCOUNTED, 0, amount);
+ } else if (mMaxDrainedPower < mComputedPower) {
+ double amount = mComputedPower - mMaxDrainedPower;
+ addEntryNoTotal(BatterySipper.DrainType.OVERCOUNTED, 0, amount);
+ }
+ }
+
+ Collections.sort(mUsageList);
+ }
+
+ private void processAppUsage() {
+ SensorManager sensorManager = (SensorManager) mContext.getSystemService(
+ Context.SENSOR_SERVICE);
+ final int which = mStatsType;
+ final int speedSteps = mPowerProfile.getNumSpeedSteps();
+ final double[] powerCpuNormal = new double[speedSteps];
+ final long[] cpuSpeedStepTimes = new long[speedSteps];
+ for (int p = 0; p < speedSteps; p++) {
+ powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);
+ }
+ final double mobilePowerPerPacket = getMobilePowerPerPacket();
+ final double mobilePowerPerMs = getMobilePowerPerMs();
+ final double wifiPowerPerPacket = getWifiPowerPerPacket();
+ long appWakelockTimeUs = 0;
+ BatterySipper osApp = null;
+ mStatsPeriod = mTypeBatteryRealtime;
+ SparseArray<? extends Uid> uidStats = mStats.getUidStats();
+ final int NU = uidStats.size();
+ for (int iu = 0; iu < NU; iu++) {
+ Uid u = uidStats.valueAt(iu);
+ double p; // in mAs
+ double power = 0; // in mAs
+ double highestDrain = 0;
+ String packageWithHighestDrain = null;
+ Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
+ long cpuTime = 0;
+ long cpuFgTime = 0;
+ long wakelockTime = 0;
+ long gpsTime = 0;
+ if (processStats.size() > 0) {
+ // Process CPU time
+ for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
+ : processStats.entrySet()) {
+ Uid.Proc ps = ent.getValue();
+ final long userTime = ps.getUserTime(which);
+ final long systemTime = ps.getSystemTime(which);
+ final long foregroundTime = ps.getForegroundTime(which);
+ cpuFgTime += foregroundTime * 10; // convert to millis
+ final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis
+ int totalTimeAtSpeeds = 0;
+ // Get the total first
+ for (int step = 0; step < speedSteps; step++) {
+ cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);
+ totalTimeAtSpeeds += cpuSpeedStepTimes[step];
+ }
+ if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1;
+ // Then compute the ratio of time spent at each speed
+ double processPower = 0;
+ for (int step = 0; step < speedSteps; step++) {
+ double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
+ if (DEBUG && ratio != 0) Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
+ + step + " ratio=" + makemAh(ratio) + " power="
+ + makemAh(ratio*tmpCpuTime*powerCpuNormal[step] / (60*60*1000)));
+ processPower += ratio * tmpCpuTime * powerCpuNormal[step];
+ }
+ cpuTime += tmpCpuTime;
+ if (DEBUG && processPower != 0) {
+ Log.d(TAG, String.format("process %s, cpu power=%s",
+ ent.getKey(), makemAh(processPower / (60*60*1000))));
+ }
+ power += processPower;
+ if (packageWithHighestDrain == null
+ || packageWithHighestDrain.startsWith("*")) {
+ highestDrain = processPower;
+ packageWithHighestDrain = ent.getKey();
+ } else if (highestDrain < processPower
+ && !ent.getKey().startsWith("*")) {
+ highestDrain = processPower;
+ packageWithHighestDrain = ent.getKey();
+ }
+ }
+ }
+ if (cpuFgTime > cpuTime) {
+ if (DEBUG && cpuFgTime > cpuTime + 10000) {
+ Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
+ }
+ cpuTime = cpuFgTime; // Statistics may not have been gathered yet.
+ }
+ power /= (60*60*1000);
+
+ // Process wake lock usage
+ Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats();
+ for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry
+ : wakelockStats.entrySet()) {
+ Uid.Wakelock wakelock = wakelockEntry.getValue();
+ // Only care about partial wake locks since full wake locks
+ // are canceled when the user turns the screen off.
+ BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
+ if (timer != null) {
+ wakelockTime += timer.getTotalTimeLocked(mRawRealtime, which);
+ }
+ }
+ appWakelockTimeUs += wakelockTime;
+ wakelockTime /= 1000; // convert to millis
+
+ // Add cost of holding a wake lock
+ p = (wakelockTime
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60*60*1000);
+ if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wake "
+ + wakelockTime + " power=" + makemAh(p));
+ power += p;
+
+ // Add cost of mobile traffic
+ final long mobileRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
+ final long mobileTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
+ final long mobileRxB = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType);
+ final long mobileTxB = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType);
+ final long mobileActive = u.getMobileRadioActiveTime(mStatsType);
+ if (mobileActive > 0) {
+ // We are tracking when the radio is up, so can use the active time to
+ // determine power use.
+ mAppMobileActive += mobileActive;
+ p = (mobilePowerPerMs * mobileActive) / 1000;
+ } else {
+ // We are not tracking when the radio is up, so must approximate power use
+ // based on the number of packets.
+ p = (mobileRx + mobileTx) * mobilePowerPerPacket;
+ }
+ if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
+ + (mobileRx+mobileTx) + " active time " + mobileActive
+ + " power=" + makemAh(p));
+ power += p;
+
+ // Add cost of wifi traffic
+ final long wifiRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType);
+ final long wifiTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType);
+ final long wifiRxB = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType);
+ final long wifiTxB = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType);
+ p = (wifiRx + wifiTx) * wifiPowerPerPacket;
+ if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi packets "
+ + (mobileRx+mobileTx) + " power=" + makemAh(p));
+ power += p;
+
+ // Add cost of keeping WIFI running.
+ long wifiRunningTimeMs = u.getWifiRunningTime(mRawRealtime, which) / 1000;
+ mAppWifiRunning += wifiRunningTimeMs;
+ p = (wifiRunningTimeMs
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000);
+ if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi running "
+ + wifiRunningTimeMs + " power=" + makemAh(p));
+ power += p;
+
+ // Add cost of WIFI scans
+ long wifiScanTimeMs = u.getWifiScanTime(mRawRealtime, which) / 1000;
+ p = (wifiScanTimeMs
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / (60*60*1000);
+ if (DEBUG) Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs
+ + " power=" + makemAh(p));
+ power += p;
+ for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
+ long batchScanTimeMs = u.getWifiBatchedScanTime(bin, mRawRealtime, which) / 1000;
+ p = ((batchScanTimeMs
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin))
+ ) / (60*60*1000);
+ if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin
+ + " time=" + batchScanTimeMs + " power=" + makemAh(p));
+ power += p;
+ }
+
+ // Process Sensor usage
+ Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
+ for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> sensorEntry
+ : sensorStats.entrySet()) {
+ Uid.Sensor sensor = sensorEntry.getValue();
+ int sensorHandle = sensor.getHandle();
+ BatteryStats.Timer timer = sensor.getSensorTime();
+ long sensorTime = timer.getTotalTimeLocked(mRawRealtime, which) / 1000;
+ double multiplier = 0;
+ switch (sensorHandle) {
+ case Uid.Sensor.GPS:
+ multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON);
+ gpsTime = sensorTime;
+ break;
+ default:
+ List<Sensor> sensorList = sensorManager.getSensorList(
+ android.hardware.Sensor.TYPE_ALL);
+ for (android.hardware.Sensor s : sensorList) {
+ if (s.getHandle() == sensorHandle) {
+ multiplier = s.getPower();
+ break;
+ }
+ }
+ }
+ p = (multiplier * sensorTime) / (60*60*1000);
+ if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle
+ + " time=" + sensorTime + " power=" + makemAh(p));
+ power += p;
+ }
+
+ if (DEBUG && power != 0) Log.d(TAG, String.format("UID %d: total power=%s",
+ u.getUid(), makemAh(power)));
+
+ // Add the app to the list if it is consuming power
+ final int userId = UserHandle.getUserId(u.getUid());
+ if (power != 0 || u.getUid() == 0) {
+ BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u,
+ new double[] {power});
+ app.cpuTime = cpuTime;
+ app.gpsTime = gpsTime;
+ app.wifiRunningTime = wifiRunningTimeMs;
+ app.cpuFgTime = cpuFgTime;
+ app.wakeLockTime = wakelockTime;
+ app.mobileRxPackets = mobileRx;
+ app.mobileTxPackets = mobileTx;
+ app.mobileActive = mobileActive / 1000;
+ app.mobileActiveCount = u.getMobileRadioActiveCount(mStatsType);
+ app.wifiRxPackets = wifiRx;
+ app.wifiTxPackets = wifiTx;
+ app.mobileRxBytes = mobileRxB;
+ app.mobileTxBytes = mobileTxB;
+ app.wifiRxBytes = wifiRxB;
+ app.wifiTxBytes = wifiTxB;
+ app.packageWithHighestDrain = packageWithHighestDrain;
+ if (u.getUid() == Process.WIFI_UID) {
+ mWifiSippers.add(app);
+ mWifiPower += power;
+ } else if (u.getUid() == Process.BLUETOOTH_UID) {
+ mBluetoothSippers.add(app);
+ mBluetoothPower += power;
+ } else if (mAsUser != UserHandle.USER_ALL && userId != mAsUser
+ && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) {
+ List<BatterySipper> list = mUserSippers.get(userId);
+ if (list == null) {
+ list = new ArrayList<BatterySipper>();
+ mUserSippers.put(userId, list);
+ }
+ list.add(app);
+ if (power != 0) {
+ Double userPower = mUserPower.get(userId);
+ if (userPower == null) {
+ userPower = power;
+ } else {
+ userPower += power;
+ }
+ mUserPower.put(userId, userPower);
+ }
+ } else {
+ mUsageList.add(app);
+ if (power > mMaxPower) mMaxPower = power;
+ mComputedPower += power;
+ }
+ if (u.getUid() == 0) {
+ osApp = app;
+ }
+ }
+ }
+
+ // The device has probably been awake for longer than the screen on
+ // time and application wake lock time would account for. Assign
+ // this remainder to the OS, if possible.
+ if (osApp != null) {
+ long wakeTimeMillis = mBatteryUptime / 1000;
+ wakeTimeMillis -= (appWakelockTimeUs / 1000)
+ + (mStats.getScreenOnTime(mRawRealtime, which) / 1000);
+ if (wakeTimeMillis > 0) {
+ double power = (wakeTimeMillis
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE))
+ / (60*60*1000);
+ if (DEBUG) Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power "
+ + makemAh(power));
+ osApp.wakeLockTime += wakeTimeMillis;
+ osApp.value += power;
+ osApp.values[0] += power;
+ if (osApp.value > mMaxPower) mMaxPower = osApp.value;
+ mComputedPower += power;
+ }
+ }
+ }
+
+ private void addPhoneUsage() {
+ long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtime, mStatsType) / 1000;
+ double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
+ * phoneOnTimeMs / (60*60*1000);
+ if (phoneOnPower != 0) {
+ BatterySipper bs = addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
+ }
+ }
+
+ private void addScreenUsage() {
+ double power = 0;
+ long screenOnTimeMs = mStats.getScreenOnTime(mRawRealtime, mStatsType) / 1000;
+ power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON);
+ final double screenFullPower =
+ mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
+ for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+ double screenBinPower = screenFullPower * (i + 0.5f)
+ / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
+ long brightnessTime = mStats.getScreenBrightnessTime(i, mRawRealtime, mStatsType)
+ / 1000;
+ double p = screenBinPower*brightnessTime;
+ if (DEBUG && p != 0) {
+ Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
+ + " power=" + makemAh(p / (60 * 60 * 1000)));
+ }
+ power += p;
+ }
+ power /= (60*60*1000); // To hours
+ if (power != 0) {
+ addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power);
+ }
+ }
+
+ private void addRadioUsage() {
+ double power = 0;
+ final int BINS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
+ long signalTimeMs = 0;
+ long noCoverageTimeMs = 0;
+ for (int i = 0; i < BINS; i++) {
+ long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, mRawRealtime, mStatsType)
+ / 1000;
+ double p = (strengthTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i))
+ / (60*60*1000);
+ if (DEBUG && p != 0) {
+ Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
+ + makemAh(p));
+ }
+ power += p;
+ signalTimeMs += strengthTimeMs;
+ if (i == 0) {
+ noCoverageTimeMs = strengthTimeMs;
+ }
+ }
+ long scanningTimeMs = mStats.getPhoneSignalScanningTime(mRawRealtime, mStatsType)
+ / 1000;
+ double p = (scanningTimeMs * mPowerProfile.getAveragePower(
+ PowerProfile.POWER_RADIO_SCANNING))
+ / (60*60*1000);
+ if (DEBUG && p != 0) {
+ Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + makemAh(p));
+ }
+ power += p;
+ long radioActiveTimeUs = mStats.getMobileRadioActiveTime(mRawRealtime, mStatsType);
+ long remainingActiveTime = (radioActiveTimeUs - mAppMobileActive) / 1000;
+ if (remainingActiveTime > 0) {
+ power += getMobilePowerPerMs() * remainingActiveTime;
+ }
+ if (power != 0) {
+ BatterySipper bs =
+ addEntry(BatterySipper.DrainType.CELL, signalTimeMs, power);
+ if (signalTimeMs != 0) {
+ bs.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
+ }
+ bs.mobileActive = remainingActiveTime;
+ bs.mobileActiveCount = mStats.getMobileRadioActiveUnknownCount(mStatsType);
+ }
+ }
+
+ private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) {
+ for (int i=0; i<from.size(); i++) {
+ BatterySipper wbs = from.get(i);
+ if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTime);
+ bs.cpuTime += wbs.cpuTime;
+ bs.gpsTime += wbs.gpsTime;
+ bs.wifiRunningTime += wbs.wifiRunningTime;
+ bs.cpuFgTime += wbs.cpuFgTime;
+ bs.wakeLockTime += wbs.wakeLockTime;
+ bs.mobileRxPackets += wbs.mobileRxPackets;
+ bs.mobileTxPackets += wbs.mobileTxPackets;
+ bs.mobileActive += wbs.mobileActive;
+ bs.mobileActiveCount += wbs.mobileActiveCount;
+ bs.wifiRxPackets += wbs.wifiRxPackets;
+ bs.wifiTxPackets += wbs.wifiTxPackets;
+ bs.mobileRxBytes += wbs.mobileRxBytes;
+ bs.mobileTxBytes += wbs.mobileTxBytes;
+ bs.wifiRxBytes += wbs.wifiRxBytes;
+ bs.wifiTxBytes += wbs.wifiTxBytes;
+ }
+ bs.computeMobilemspp();
+ }
+
+ private void addWiFiUsage() {
+ long onTimeMs = mStats.getWifiOnTime(mRawRealtime, mStatsType) / 1000;
+ long runningTimeMs = mStats.getGlobalWifiRunningTime(mRawRealtime, mStatsType) / 1000;
+ if (DEBUG) Log.d(TAG, "WIFI runningTime=" + runningTimeMs
+ + " app runningTime=" + mAppWifiRunning);
+ runningTimeMs -= mAppWifiRunning;
+ if (runningTimeMs < 0) runningTimeMs = 0;
+ double wifiPower = (onTimeMs * 0 /* TODO */
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)
+ + runningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON))
+ / (60*60*1000);
+ if (DEBUG && wifiPower != 0) {
+ Log.d(TAG, "Wifi: time=" + runningTimeMs + " power=" + makemAh(wifiPower));
+ }
+ if ((wifiPower+mWifiPower) != 0) {
+ BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, runningTimeMs,
+ wifiPower + mWifiPower);
+ aggregateSippers(bs, mWifiSippers, "WIFI");
+ }
+ }
+
+ private void addIdleUsage() {
+ long idleTimeMs = (mTypeBatteryRealtime
+ - mStats.getScreenOnTime(mRawRealtime, mStatsType)) / 1000;
+ double idlePower = (idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE))
+ / (60*60*1000);
+ if (DEBUG && idlePower != 0) {
+ Log.d(TAG, "Idle: time=" + idleTimeMs + " power=" + makemAh(idlePower));
+ }
+ if (idlePower != 0) {
+ addEntry(BatterySipper.DrainType.IDLE, idleTimeMs, idlePower);
+ }
+ }
+
+ private void addBluetoothUsage() {
+ long btOnTimeMs = mStats.getBluetoothOnTime(mRawRealtime, mStatsType) / 1000;
+ double btPower = btOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_ON)
+ / (60*60*1000);
+ if (DEBUG && btPower != 0) {
+ Log.d(TAG, "Bluetooth: time=" + btOnTimeMs + " power=" + makemAh(btPower));
+ }
+ int btPingCount = mStats.getBluetoothPingCount();
+ double pingPower = (btPingCount
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_AT_CMD))
+ / (60*60*1000);
+ if (DEBUG && pingPower != 0) {
+ Log.d(TAG, "Bluetooth ping: count=" + btPingCount + " power=" + makemAh(pingPower));
+ }
+ btPower += pingPower;
+ if ((btPower+mBluetoothPower) != 0) {
+ BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, btOnTimeMs,
+ btPower + mBluetoothPower);
+ aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
+ }
+ }
+
+ private void addUserUsage() {
+ for (int i=0; i<mUserSippers.size(); i++) {
+ final int userId = mUserSippers.keyAt(i);
+ final List<BatterySipper> sippers = mUserSippers.valueAt(i);
+ Double userPower = mUserPower.get(userId);
+ double power = (userPower != null) ? userPower : 0.0;
+ BatterySipper bs = addEntry(BatterySipper.DrainType.USER, 0, power);
+ bs.userId = userId;
+ aggregateSippers(bs, sippers, "User");
+ }
+ }
+
+ /**
+ * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio.
+ */
+ private double getMobilePowerPerPacket() {
+ final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
+ final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
+ / 3600;
+
+ final long mobileRx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
+ final long mobileTx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
+ final long mobileData = mobileRx + mobileTx;
+
+ final long radioDataUptimeMs
+ = mStats.getMobileRadioActiveTime(mRawRealtime, mStatsType) / 1000;
+ final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0)
+ ? (mobileData / (double)radioDataUptimeMs)
+ : (((double)MOBILE_BPS) / 8 / 2048);
+
+ return (MOBILE_POWER / mobilePps) / (60*60);
+ }
+
+ /**
+ * Return estimated power (in mAs) of keeping the radio up
+ */
+ private double getMobilePowerPerMs() {
+ return mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) / (60*60*1000);
+ }
+
+ /**
+ * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio.
+ */
+ private double getWifiPowerPerPacket() {
+ final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
+ final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
+ / 3600;
+ return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60);
+ }
+
+ private void processMiscUsage() {
+ addUserUsage();
+ addPhoneUsage();
+ addScreenUsage();
+ addWiFiUsage();
+ addBluetoothUsage();
+ addIdleUsage(); // Not including cellular idle power
+ // Don't compute radio usage if it's a wifi-only device
+ ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) {
+ addRadioUsage();
+ }
+ }
+
+ private BatterySipper addEntry(DrainType drainType, long time, double power) {
+ mComputedPower += power;
+ return addEntryNoTotal(drainType, time, power);
+ }
+
+ private BatterySipper addEntryNoTotal(DrainType drainType, long time, double power) {
+ if (power > mMaxPower) mMaxPower = power;
+ BatterySipper bs = new BatterySipper(drainType, null, new double[] {power});
+ bs.usageTime = time;
+ mUsageList.add(bs);
+ return bs;
+ }
+
+ public List<BatterySipper> getUsageList() {
+ return mUsageList;
+ }
+
+ public List<BatterySipper> getMobilemsppList() {
+ return mMobilemsppList;
+ }
+
+ public long getStatsPeriod() { return mStatsPeriod; }
+
+ public int getStatsType() { return mStatsType; };
+
+ public double getMaxPower() { return mMaxPower; }
+
+ public double getTotalPower() { return mTotalPower; }
+
+ public double getComputedPower() { return mComputedPower; }
+
+ public double getMinDrainedPower() {
+ return mMinDrainedPower;
+ }
+
+ public double getMaxDrainedPower() {
+ return mMaxDrainedPower;
+ }
+
+ public long getBatteryTimeRemaining() { return mBatteryTimeRemaining; }
+
+ public long getChargeTimeRemaining() { return mChargeTimeRemaining; }
+
+ private void load() {
+ if (mBatteryInfo == null) {
+ return;
+ }
+ try {
+ byte[] data = mBatteryInfo.getStatistics();
+ Parcel parcel = Parcel.obtain();
+ parcel.unmarshall(data, 0, data.length);
+ parcel.setDataPosition(0);
+ BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
+ .createFromParcel(parcel);
+ stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED);
+ mStats = stats;
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException:", e);
+ }
+ if (mCollectBatteryBroadcast) {
+ mBatteryBroadcast = mContext.registerReceiver(null,
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 8728610..f63fa8a 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -16,12 +16,15 @@
package com.android.internal.os;
+import static android.net.NetworkStats.UID_ALL;
import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
+import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkStats;
+import android.os.BadParcelableException;
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.FileUtils;
@@ -35,6 +38,7 @@ import android.os.Process;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.WorkSource;
+import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
@@ -44,24 +48,23 @@ import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.JournaledFile;
-import com.google.android.collect.Sets;
-import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -85,7 +88,7 @@ public final class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 67 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 104 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -141,6 +144,11 @@ public final class BatteryStatsImpl extends BatteryStats {
private BatteryCallback mCallback;
/**
+ * Mapping isolated uids to the actual owning app uid.
+ */
+ final SparseIntArray mIsolatedUids = new SparseIntArray();
+
+ /**
* The statistics we have collected organized by uids.
*/
final SparseArray<BatteryStatsImpl.Uid> mUidStats =
@@ -167,13 +175,24 @@ public final class BatteryStatsImpl extends BatteryStats {
// These are the objects that will want to do something when the device
// is unplugged from power.
- final ArrayList<Unpluggable> mUnpluggables = new ArrayList<Unpluggable>();
+ final TimeBase mOnBatteryTimeBase = new TimeBase();
+
+ // These are the objects that will want to do something when the device
+ // is unplugged from power *and* the screen is off.
+ final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase();
+
+ // Set to true when we want to distribute CPU across wakelocks for the next
+ // CPU update, even if we aren't currently running wake locks.
+ boolean mDistributeWakelockCpu;
boolean mShuttingDown;
+ HashMap<String, SparseBooleanArray>[] mActiveEvents
+ = (HashMap<String, SparseBooleanArray>[]) new HashMap[HistoryItem.EVENT_COUNT];
+
long mHistoryBaseTime;
boolean mHaveBatteryLevel = false;
- boolean mRecordingHistory = true;
+ boolean mRecordingHistory = false;
int mNumHistoryItems;
static final int MAX_HISTORY_BUFFER = 128*1024; // 128KB
@@ -182,9 +201,18 @@ public final class BatteryStatsImpl extends BatteryStats {
final HistoryItem mHistoryLastWritten = new HistoryItem();
final HistoryItem mHistoryLastLastWritten = new HistoryItem();
final HistoryItem mHistoryReadTmp = new HistoryItem();
+ final HistoryItem mHistoryAddTmp = new HistoryItem();
+ final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<HistoryTag, Integer>();
+ String[] mReadHistoryStrings;
+ int[] mReadHistoryUids;
+ int mReadHistoryChars;
+ int mNextHistoryTagIdx = 0;
+ int mNumHistoryTagChars = 0;
int mHistoryBufferLastPos = -1;
boolean mHistoryOverflow = false;
- long mLastHistoryTime = 0;
+ long mLastHistoryElapsedRealtime = 0;
+ long mTrackRunningHistoryElapsedRealtime = 0;
+ long mTrackRunningHistoryUptime = 0;
final HistoryItem mHistoryCur = new HistoryItem();
@@ -199,17 +227,15 @@ public final class BatteryStatsImpl extends BatteryStats {
int mStartCount;
- long mBatteryUptime;
- long mBatteryLastUptime;
- long mBatteryRealtime;
- long mBatteryLastRealtime;
+ long mStartClockTime;
long mUptime;
long mUptimeStart;
- long mLastUptime;
long mRealtime;
long mRealtimeStart;
- long mLastRealtime;
+
+ int mWakeLockNesting;
+ boolean mWakeLockImportant;
boolean mScreenOn;
StopwatchTimer mScreenOnTimer;
@@ -239,19 +265,33 @@ public final class BatteryStatsImpl extends BatteryStats {
final StopwatchTimer[] mPhoneDataConnectionsTimer =
new StopwatchTimer[NUM_DATA_CONNECTION_TYPES];
- final LongSamplingCounter[] mNetworkActivityCounters =
+ final LongSamplingCounter[] mNetworkByteActivityCounters =
+ new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+ final LongSamplingCounter[] mNetworkPacketActivityCounters =
new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
boolean mWifiOn;
StopwatchTimer mWifiOnTimer;
- int mWifiOnUid = -1;
boolean mGlobalWifiRunning;
StopwatchTimer mGlobalWifiRunningTimer;
+ int mWifiState = -1;
+ final StopwatchTimer[] mWifiStateTimer = new StopwatchTimer[NUM_WIFI_STATES];
+
boolean mBluetoothOn;
StopwatchTimer mBluetoothOnTimer;
+ int mBluetoothState = -1;
+ final StopwatchTimer[] mBluetoothStateTimer = new StopwatchTimer[NUM_BLUETOOTH_STATES];
+
+ int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+ StopwatchTimer mMobileRadioActiveTimer;
+ StopwatchTimer mMobileRadioActivePerAppTimer;
+ LongSamplingCounter mMobileRadioActiveAdjustedTime;
+ LongSamplingCounter mMobileRadioActiveUnknownTime;
+ LongSamplingCounter mMobileRadioActiveUnknownCount;
+
/** Bluetooth headset object */
BluetoothHeadset mBtHeadset;
@@ -261,20 +301,15 @@ public final class BatteryStatsImpl extends BatteryStats {
*/
boolean mOnBattery;
boolean mOnBatteryInternal;
- long mTrackBatteryPastUptime;
- long mTrackBatteryUptimeStart;
- long mTrackBatteryPastRealtime;
- long mTrackBatteryRealtimeStart;
-
- long mUnpluggedBatteryUptime;
- long mUnpluggedBatteryRealtime;
/*
* These keep track of battery levels (1-100) at the last plug event and the last unplug event.
*/
int mDischargeStartLevel;
int mDischargeUnplugLevel;
+ int mDischargePlugLevel;
int mDischargeCurrentLevel;
+ int mCurrentBatteryLevel;
int mLowDischargeAmountSinceCharge;
int mHighDischargeAmountSinceCharge;
int mDischargeScreenOnUnplugLevel;
@@ -284,10 +319,19 @@ public final class BatteryStatsImpl extends BatteryStats {
int mDischargeAmountScreenOff;
int mDischargeAmountScreenOffSinceCharge;
- long mLastWriteTime = 0; // Milliseconds
+ static final int MAX_LEVEL_STEPS = 100;
- private long mRadioDataUptime;
- private long mRadioDataStart;
+ int mLastDischargeStepLevel;
+ long mLastDischargeStepTime;
+ int mNumDischargeStepDurations;
+ final long[] mDischargeStepDurations = new long[MAX_LEVEL_STEPS];
+
+ int mLastChargeStepLevel;
+ long mLastChargeStepTime;
+ int mNumChargeStepDurations;
+ final long[] mChargeStepDurations = new long[MAX_LEVEL_STEPS];
+
+ long mLastWriteTime = 0; // Milliseconds
private int mBluetoothPingCount;
private int mBluetoothPingStart = -1;
@@ -302,12 +346,21 @@ public final class BatteryStatsImpl extends BatteryStats {
private final HashMap<String, SamplingTimer> mKernelWakelockStats =
new HashMap<String, SamplingTimer>();
- public Map<String, ? extends SamplingTimer> getKernelWakelockStats() {
+ public Map<String, ? extends Timer> getKernelWakelockStats() {
return mKernelWakelockStats;
}
private static int sKernelWakelockUpdateVersion = 0;
+ String mLastWakeupReason = null;
+ long mLastWakeupUptimeMs = 0;
+ private final HashMap<String, LongSamplingCounter> mWakeupReasonStats =
+ new HashMap<String, LongSamplingCounter>();
+
+ public Map<String, ? extends LongCounter> getWakeupReasonStats() {
+ return mWakeupReasonStats;
+ }
+
private static final int[] PROC_WAKELOCKS_FORMAT = new int[] {
Process.PROC_TAB_TERM|Process.PROC_OUT_STRING| // 0: name
Process.PROC_QUOTES,
@@ -340,15 +393,18 @@ public final class BatteryStatsImpl extends BatteryStats {
private final Map<String, KernelWakelockStats> mProcWakelockFileStats =
new HashMap<String, KernelWakelockStats>();
- private HashMap<String, Integer> mUidCache = new HashMap<String, Integer>();
-
private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory();
- private NetworkStats mLastSnapshot;
+ private NetworkStats mCurMobileSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
+ private NetworkStats mLastMobileSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
+ private NetworkStats mCurWifiSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
+ private NetworkStats mLastWifiSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
+ private NetworkStats mTmpNetworkStats;
+ private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry();
@GuardedBy("this")
- private HashSet<String> mMobileIfaces = Sets.newHashSet();
+ private String[] mMobileIfaces = new String[0];
@GuardedBy("this")
- private HashSet<String> mWifiIfaces = Sets.newHashSet();
+ private String[] mWifiIfaces = new String[0];
// For debugging
public BatteryStatsImpl() {
@@ -356,35 +412,228 @@ public final class BatteryStatsImpl extends BatteryStats {
mHandler = null;
}
- public static interface Unpluggable {
- void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime);
- void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime);
+ public static interface TimeBaseObs {
+ void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime);
+ void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime);
+ }
+
+ static class TimeBase {
+ private final ArrayList<TimeBaseObs> mObservers = new ArrayList<TimeBaseObs>();
+
+ private long mUptime;
+ private long mRealtime;
+
+ private boolean mRunning;
+
+ private long mPastUptime;
+ private long mUptimeStart;
+ private long mPastRealtime;
+ private long mRealtimeStart;
+ private long mUnpluggedUptime;
+ private long mUnpluggedRealtime;
+
+ public void dump(PrintWriter pw, String prefix) {
+ StringBuilder sb = new StringBuilder(128);
+ pw.print(prefix); pw.print("mRunning="); pw.println(mRunning);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append("mUptime=");
+ formatTimeMs(sb, mUptime / 1000);
+ pw.println(sb.toString());
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append("mRealtime=");
+ formatTimeMs(sb, mRealtime / 1000);
+ pw.println(sb.toString());
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append("mPastUptime=");
+ formatTimeMs(sb, mPastUptime / 1000); sb.append("mUptimeStart=");
+ formatTimeMs(sb, mUptimeStart / 1000);
+ sb.append("mUnpluggedUptime="); formatTimeMs(sb, mUnpluggedUptime / 1000);
+ pw.println(sb.toString());
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append("mPastRealtime=");
+ formatTimeMs(sb, mPastRealtime / 1000); sb.append("mRealtimeStart=");
+ formatTimeMs(sb, mRealtimeStart / 1000);
+ sb.append("mUnpluggedRealtime="); formatTimeMs(sb, mUnpluggedRealtime / 1000);
+ pw.println(sb.toString());
+ }
+
+ public void add(TimeBaseObs observer) {
+ mObservers.add(observer);
+ }
+
+ public void remove(TimeBaseObs observer) {
+ if (!mObservers.remove(observer)) {
+ Slog.wtf(TAG, "Removed unknown observer: " + observer);
+ }
+ }
+
+ public void init(long uptime, long realtime) {
+ mRealtime = 0;
+ mUptime = 0;
+ mPastUptime = 0;
+ mPastRealtime = 0;
+ mUptimeStart = uptime;
+ mRealtimeStart = realtime;
+ mUnpluggedUptime = getUptime(mUptimeStart);
+ mUnpluggedRealtime = getRealtime(mRealtimeStart);
+ }
+
+ public void reset(long uptime, long realtime) {
+ if (!mRunning) {
+ mPastUptime = 0;
+ mPastRealtime = 0;
+ } else {
+ mUptimeStart = uptime;
+ mRealtimeStart = realtime;
+ mUnpluggedUptime = getUptime(uptime);
+ mUnpluggedRealtime = getRealtime(realtime);
+ }
+ }
+
+ public long computeUptime(long curTime, int which) {
+ switch (which) {
+ case STATS_SINCE_CHARGED:
+ return mUptime + getUptime(curTime);
+ case STATS_CURRENT:
+ return getUptime(curTime);
+ case STATS_SINCE_UNPLUGGED:
+ return getUptime(curTime) - mUnpluggedUptime;
+ }
+ return 0;
+ }
+
+ public long computeRealtime(long curTime, int which) {
+ switch (which) {
+ case STATS_SINCE_CHARGED:
+ return mRealtime + getRealtime(curTime);
+ case STATS_CURRENT:
+ return getRealtime(curTime);
+ case STATS_SINCE_UNPLUGGED:
+ return getRealtime(curTime) - mUnpluggedRealtime;
+ }
+ return 0;
+ }
+
+ public long getUptime(long curTime) {
+ long time = mPastUptime;
+ if (mRunning) {
+ time += curTime - mUptimeStart;
+ }
+ return time;
+ }
+
+ public long getRealtime(long curTime) {
+ long time = mPastRealtime;
+ if (mRunning) {
+ time += curTime - mRealtimeStart;
+ }
+ return time;
+ }
+
+ public long getUptimeStart() {
+ return mUptimeStart;
+ }
+
+ public long getRealtimeStart() {
+ return mRealtimeStart;
+ }
+
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ public boolean setRunning(boolean running, long uptime, long realtime) {
+ if (mRunning != running) {
+ mRunning = running;
+ if (running) {
+ mUptimeStart = uptime;
+ mRealtimeStart = realtime;
+ long batteryUptime = mUnpluggedUptime = getUptime(uptime);
+ long batteryRealtime = mUnpluggedRealtime = getRealtime(realtime);
+
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onTimeStarted(realtime, batteryUptime, batteryRealtime);
+ }
+ } else {
+ mPastUptime += uptime - mUptimeStart;
+ mPastRealtime += realtime - mRealtimeStart;
+
+ long batteryUptime = getUptime(uptime);
+ long batteryRealtime = getRealtime(realtime);
+
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onTimeStopped(realtime, batteryUptime, batteryRealtime);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void readSummaryFromParcel(Parcel in) {
+ mUptime = in.readLong();
+ mRealtime = in.readLong();
+ }
+
+ public void writeSummaryToParcel(Parcel out, long uptime, long realtime) {
+ out.writeLong(computeUptime(uptime, STATS_SINCE_CHARGED));
+ out.writeLong(computeRealtime(realtime, STATS_SINCE_CHARGED));
+ }
+
+ public void readFromParcel(Parcel in) {
+ mRunning = false;
+ mUptime = in.readLong();
+ mPastUptime = in.readLong();
+ mUptimeStart = in.readLong();
+ mRealtime = in.readLong();
+ mPastRealtime = in.readLong();
+ mRealtimeStart = in.readLong();
+ mUnpluggedUptime = in.readLong();
+ mUnpluggedRealtime = in.readLong();
+ }
+
+ public void writeToParcel(Parcel out, long uptime, long realtime) {
+ final long runningUptime = getUptime(uptime);
+ final long runningRealtime = getRealtime(realtime);
+ out.writeLong(mUptime);
+ out.writeLong(runningUptime);
+ out.writeLong(mUptimeStart);
+ out.writeLong(mRealtime);
+ out.writeLong(runningRealtime);
+ out.writeLong(mRealtimeStart);
+ out.writeLong(mUnpluggedUptime);
+ out.writeLong(mUnpluggedRealtime);
+ }
}
/**
* State for keeping track of counting information.
*/
- public static class Counter extends BatteryStats.Counter implements Unpluggable {
+ public static class Counter extends BatteryStats.Counter implements TimeBaseObs {
final AtomicInteger mCount = new AtomicInteger();
- final ArrayList<Unpluggable> mUnpluggables;
+ final TimeBase mTimeBase;
int mLoadedCount;
int mLastCount;
int mUnpluggedCount;
int mPluggedCount;
- Counter(ArrayList<Unpluggable> unpluggables, Parcel in) {
- mUnpluggables = unpluggables;
+ Counter(TimeBase timeBase, Parcel in) {
+ mTimeBase = timeBase;
mPluggedCount = in.readInt();
mCount.set(mPluggedCount);
mLoadedCount = in.readInt();
mLastCount = 0;
mUnpluggedCount = in.readInt();
- unpluggables.add(this);
+ timeBase.add(this);
}
- Counter(ArrayList<Unpluggable> unpluggables) {
- mUnpluggables = unpluggables;
- unpluggables.add(this);
+ Counter(TimeBase timeBase) {
+ mTimeBase = timeBase;
+ timeBase.add(this);
}
public void writeToParcel(Parcel out) {
@@ -393,12 +642,12 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(mUnpluggedCount);
}
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
mUnpluggedCount = mPluggedCount;
mCount.set(mPluggedCount);
}
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
mPluggedCount = mCount.get();
}
@@ -420,16 +669,11 @@ public final class BatteryStatsImpl extends BatteryStats {
@Override
public int getCountLocked(int which) {
- int val;
- if (which == STATS_LAST) {
- val = mLastCount;
- } else {
- val = mCount.get();
- if (which == STATS_SINCE_UNPLUGGED) {
- val -= mUnpluggedCount;
- } else if (which != STATS_SINCE_CHARGED) {
- val -= mLoadedCount;
- }
+ int val = mCount.get();
+ if (which == STATS_SINCE_UNPLUGGED) {
+ val -= mUnpluggedCount;
+ } else if (which != STATS_SINCE_CHARGED) {
+ val -= mLoadedCount;
}
return val;
@@ -458,7 +702,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
void detach() {
- mUnpluggables.remove(this);
+ mTimeBase.remove(this);
}
void writeSummaryFromParcelLocked(Parcel out) {
@@ -475,12 +719,12 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public static class SamplingCounter extends Counter {
- SamplingCounter(ArrayList<Unpluggable> unpluggables, Parcel in) {
- super(unpluggables, in);
+ SamplingCounter(TimeBase timeBase, Parcel in) {
+ super(timeBase, in);
}
- SamplingCounter(ArrayList<Unpluggable> unpluggables) {
- super(unpluggables);
+ SamplingCounter(TimeBase timeBase) {
+ super(timeBase);
}
public void addCountAtomic(long count) {
@@ -488,27 +732,27 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- public static class LongSamplingCounter implements Unpluggable {
- final ArrayList<Unpluggable> mUnpluggables;
+ public static class LongSamplingCounter extends LongCounter implements TimeBaseObs {
+ final TimeBase mTimeBase;
long mCount;
long mLoadedCount;
long mLastCount;
long mUnpluggedCount;
long mPluggedCount;
- LongSamplingCounter(ArrayList<Unpluggable> unpluggables, Parcel in) {
- mUnpluggables = unpluggables;
+ LongSamplingCounter(TimeBase timeBase, Parcel in) {
+ mTimeBase = timeBase;
mPluggedCount = in.readLong();
mCount = mPluggedCount;
mLoadedCount = in.readLong();
mLastCount = 0;
mUnpluggedCount = in.readLong();
- unpluggables.add(this);
+ timeBase.add(this);
}
- LongSamplingCounter(ArrayList<Unpluggable> unpluggables) {
- mUnpluggables = unpluggables;
- unpluggables.add(this);
+ LongSamplingCounter(TimeBase timeBase) {
+ mTimeBase = timeBase;
+ timeBase.add(this);
}
public void writeToParcel(Parcel out) {
@@ -518,32 +762,35 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
mUnpluggedCount = mPluggedCount;
mCount = mPluggedCount;
}
@Override
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
mPluggedCount = mCount;
}
public long getCountLocked(int which) {
- long val;
- if (which == STATS_LAST) {
- val = mLastCount;
- } else {
- val = mCount;
- if (which == STATS_SINCE_UNPLUGGED) {
- val -= mUnpluggedCount;
- } else if (which != STATS_SINCE_CHARGED) {
- val -= mLoadedCount;
- }
+ long val = mCount;
+ if (which == STATS_SINCE_UNPLUGGED) {
+ val -= mUnpluggedCount;
+ } else if (which != STATS_SINCE_CHARGED) {
+ val -= mLoadedCount;
}
return val;
}
+ @Override
+ public void logState(Printer pw, String prefix) {
+ pw.println(prefix + "mCount=" + mCount
+ + " mLoadedCount=" + mLoadedCount + " mLastCount=" + mLastCount
+ + " mUnpluggedCount=" + mUnpluggedCount
+ + " mPluggedCount=" + mPluggedCount);
+ }
+
void addCountLocked(long count) {
mCount += count;
}
@@ -560,7 +807,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
void detach() {
- mUnpluggables.remove(this);
+ mTimeBase.remove(this);
}
void writeSummaryFromParcelLocked(Parcel out) {
@@ -578,9 +825,9 @@ public final class BatteryStatsImpl extends BatteryStats {
/**
* State for keeping track of timing information.
*/
- public static abstract class Timer extends BatteryStats.Timer implements Unpluggable {
+ public static abstract class Timer extends BatteryStats.Timer implements TimeBaseObs {
final int mType;
- final ArrayList<Unpluggable> mUnpluggables;
+ final TimeBase mTimeBase;
int mCount;
int mLoadedCount;
@@ -619,12 +866,12 @@ public final class BatteryStatsImpl extends BatteryStats {
/**
* Constructs from a parcel.
* @param type
- * @param unpluggables
+ * @param timeBase
* @param in
*/
- Timer(int type, ArrayList<Unpluggable> unpluggables, Parcel in) {
+ Timer(int type, TimeBase timeBase, Parcel in) {
mType = type;
- mUnpluggables = unpluggables;
+ mTimeBase = timeBase;
mCount = in.readInt();
mLoadedCount = in.readInt();
@@ -634,13 +881,13 @@ public final class BatteryStatsImpl extends BatteryStats {
mLoadedTime = in.readLong();
mLastTime = 0;
mUnpluggedTime = in.readLong();
- unpluggables.add(this);
+ timeBase.add(this);
}
- Timer(int type, ArrayList<Unpluggable> unpluggables) {
+ Timer(int type, TimeBase timeBase) {
mType = type;
- mUnpluggables = unpluggables;
- unpluggables.add(this);
+ mTimeBase = timeBase;
+ timeBase.add(this);
}
protected abstract long computeRunTimeLocked(long curBatteryRealtime);
@@ -651,7 +898,7 @@ public final class BatteryStatsImpl extends BatteryStats {
* Clear state of this timer. Returns true if the timer is inactive
* so can be completely dropped.
*/
- boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
+ boolean reset(boolean detachIfReset) {
mTotalTime = mLoadedTime = mLastTime = 0;
mCount = mLoadedCount = mLastCount = 0;
if (detachIfReset) {
@@ -661,25 +908,25 @@ public final class BatteryStatsImpl extends BatteryStats {
}
void detach() {
- mUnpluggables.remove(this);
+ mTimeBase.remove(this);
}
- public void writeToParcel(Parcel out, long batteryRealtime) {
+ public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
out.writeInt(mCount);
out.writeInt(mLoadedCount);
out.writeInt(mUnpluggedCount);
- out.writeLong(computeRunTimeLocked(batteryRealtime));
+ out.writeLong(computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs)));
out.writeLong(mLoadedTime);
out.writeLong(mUnpluggedTime);
}
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStarted(long elapsedRealtime, long timeBaseUptime, long baseRealtime) {
if (DEBUG && mType < 0) {
- Log.v(TAG, "unplug #" + mType + ": realtime=" + batteryRealtime
+ Log.v(TAG, "unplug #" + mType + ": realtime=" + baseRealtime
+ " old mUnpluggedTime=" + mUnpluggedTime
+ " old mUnpluggedCount=" + mUnpluggedCount);
}
- mUnpluggedTime = computeRunTimeLocked(batteryRealtime);
+ mUnpluggedTime = computeRunTimeLocked(baseRealtime);
mUnpluggedCount = mCount;
if (DEBUG && mType < 0) {
Log.v(TAG, "unplug #" + mType
@@ -688,12 +935,12 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
if (DEBUG && mType < 0) {
- Log.v(TAG, "plug #" + mType + ": realtime=" + batteryRealtime
+ Log.v(TAG, "plug #" + mType + ": realtime=" + baseRealtime
+ " old mTotalTime=" + mTotalTime);
}
- mTotalTime = computeRunTimeLocked(batteryRealtime);
+ mTotalTime = computeRunTimeLocked(baseRealtime);
mCount = computeCurrentCountLocked();
if (DEBUG && mType < 0) {
Log.v(TAG, "plug #" + mType
@@ -707,29 +954,23 @@ public final class BatteryStatsImpl extends BatteryStats {
* @param out the Parcel to be written to.
* @param timer a Timer, or null.
*/
- public static void writeTimerToParcel(Parcel out, Timer timer,
- long batteryRealtime) {
+ public static void writeTimerToParcel(Parcel out, Timer timer, long elapsedRealtimeUs) {
if (timer == null) {
out.writeInt(0); // indicates null
return;
}
out.writeInt(1); // indicates non-null
- timer.writeToParcel(out, batteryRealtime);
+ timer.writeToParcel(out, elapsedRealtimeUs);
}
@Override
- public long getTotalTimeLocked(long batteryRealtime, int which) {
- long val;
- if (which == STATS_LAST) {
- val = mLastTime;
- } else {
- val = computeRunTimeLocked(batteryRealtime);
- if (which == STATS_SINCE_UNPLUGGED) {
- val -= mUnpluggedTime;
- } else if (which != STATS_SINCE_CHARGED) {
- val -= mLoadedTime;
- }
+ public long getTotalTimeLocked(long elapsedRealtimeUs, int which) {
+ long val = computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs));
+ if (which == STATS_SINCE_UNPLUGGED) {
+ val -= mUnpluggedTime;
+ } else if (which != STATS_SINCE_CHARGED) {
+ val -= mLoadedTime;
}
return val;
@@ -737,16 +978,11 @@ public final class BatteryStatsImpl extends BatteryStats {
@Override
public int getCountLocked(int which) {
- int val;
- if (which == STATS_LAST) {
- val = mLastCount;
- } else {
- val = computeCurrentCountLocked();
- if (which == STATS_SINCE_UNPLUGGED) {
- val -= mUnpluggedCount;
- } else if (which != STATS_SINCE_CHARGED) {
- val -= mLoadedCount;
- }
+ int val = computeCurrentCountLocked();
+ if (which == STATS_SINCE_UNPLUGGED) {
+ val -= mUnpluggedCount;
+ } else if (which != STATS_SINCE_CHARGED) {
+ val -= mLoadedCount;
}
return val;
@@ -763,16 +999,15 @@ public final class BatteryStatsImpl extends BatteryStats {
}
- void writeSummaryFromParcelLocked(Parcel out, long batteryRealtime) {
- long runTime = computeRunTimeLocked(batteryRealtime);
- // Divide by 1000 for backwards compatibility
- out.writeLong((runTime + 500) / 1000);
+ void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) {
+ long runTime = computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs));
+ out.writeLong(runTime);
out.writeInt(mCount);
}
void readSummaryFromParcelLocked(Parcel in) {
// Multiply by 1000 for backwards compatibility
- mTotalTime = mLoadedTime = in.readLong() * 1000;
+ mTotalTime = mLoadedTime = in.readLong();
mLastTime = 0;
mUnpluggedTime = mTotalTime;
mCount = mLoadedCount = in.readInt();
@@ -809,7 +1044,7 @@ public final class BatteryStatsImpl extends BatteryStats {
/**
* Whether we are currently in a discharge cycle.
*/
- boolean mInDischarge;
+ boolean mTimeBaseRunning;
/**
* Whether we are currently recording reported values.
@@ -821,21 +1056,20 @@ public final class BatteryStatsImpl extends BatteryStats {
*/
int mUpdateVersion;
- SamplingTimer(ArrayList<Unpluggable> unpluggables, boolean inDischarge, Parcel in) {
- super(0, unpluggables, in);
+ SamplingTimer(TimeBase timeBase, Parcel in) {
+ super(0, timeBase, in);
mCurrentReportedCount = in.readInt();
mUnpluggedReportedCount = in.readInt();
mCurrentReportedTotalTime = in.readLong();
mUnpluggedReportedTotalTime = in.readLong();
mTrackingReportedValues = in.readInt() == 1;
- mInDischarge = inDischarge;
+ mTimeBaseRunning = timeBase.isRunning();
}
- SamplingTimer(ArrayList<Unpluggable> unpluggables, boolean inDischarge,
- boolean trackReportedValues) {
- super(0, unpluggables);
+ SamplingTimer(TimeBase timeBase, boolean trackReportedValues) {
+ super(0, timeBase);
mTrackingReportedValues = trackReportedValues;
- mInDischarge = inDischarge;
+ mTimeBaseRunning = timeBase.isRunning();
}
public void setStale() {
@@ -853,7 +1087,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void updateCurrentReportedCount(int count) {
- if (mInDischarge && mUnpluggedReportedCount == 0) {
+ if (mTimeBaseRunning && mUnpluggedReportedCount == 0) {
// Updating the reported value for the first time.
mUnpluggedReportedCount = count;
// If we are receiving an update update mTrackingReportedValues;
@@ -863,7 +1097,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void updateCurrentReportedTotalTime(long totalTime) {
- if (mInDischarge && mUnpluggedReportedTotalTime == 0) {
+ if (mTimeBaseRunning && mUnpluggedReportedTotalTime == 0) {
// Updating the reported value for the first time.
mUnpluggedReportedTotalTime = totalTime;
// If we are receiving an update update mTrackingReportedValues;
@@ -872,18 +1106,18 @@ public final class BatteryStatsImpl extends BatteryStats {
mCurrentReportedTotalTime = totalTime;
}
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
- super.unplug(elapsedRealtime, batteryUptime, batteryRealtime);
+ public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
+ super.onTimeStarted(elapsedRealtime, baseUptime, baseRealtime);
if (mTrackingReportedValues) {
mUnpluggedReportedTotalTime = mCurrentReportedTotalTime;
mUnpluggedReportedCount = mCurrentReportedCount;
}
- mInDischarge = true;
+ mTimeBaseRunning = true;
}
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
- super.plug(elapsedRealtime, batteryUptime, batteryRealtime);
- mInDischarge = false;
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
+ super.onTimeStopped(elapsedRealtime, baseUptime, baseRealtime);
+ mTimeBaseRunning = false;
}
public void logState(Printer pw, String prefix) {
@@ -895,17 +1129,17 @@ public final class BatteryStatsImpl extends BatteryStats {
}
protected long computeRunTimeLocked(long curBatteryRealtime) {
- return mTotalTime + (mInDischarge && mTrackingReportedValues
+ return mTotalTime + (mTimeBaseRunning && mTrackingReportedValues
? mCurrentReportedTotalTime - mUnpluggedReportedTotalTime : 0);
}
protected int computeCurrentCountLocked() {
- return mCount + (mInDischarge && mTrackingReportedValues
+ return mCount + (mTimeBaseRunning && mTrackingReportedValues
? mCurrentReportedCount - mUnpluggedReportedCount : 0);
}
- public void writeToParcel(Parcel out, long batteryRealtime) {
- super.writeToParcel(out, batteryRealtime);
+ public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+ super.writeToParcel(out, elapsedRealtimeUs);
out.writeInt(mCurrentReportedCount);
out.writeInt(mUnpluggedReportedCount);
out.writeLong(mCurrentReportedTotalTime);
@@ -913,8 +1147,8 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(mTrackingReportedValues ? 1 : 0);
}
- boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
- super.reset(stats, detachIfReset);
+ boolean reset(boolean detachIfReset) {
+ super.reset(detachIfReset);
setStale();
return true;
}
@@ -956,45 +1190,43 @@ public final class BatteryStatsImpl extends BatteryStats {
*/
boolean mInDischarge;
- BatchTimer(Uid uid, int type, ArrayList<Unpluggable> unpluggables,
- boolean inDischarge, Parcel in) {
- super(type, unpluggables, in);
+ BatchTimer(Uid uid, int type, TimeBase timeBase, Parcel in) {
+ super(type, timeBase, in);
mUid = uid;
mLastAddedTime = in.readLong();
mLastAddedDuration = in.readLong();
- mInDischarge = inDischarge;
+ mInDischarge = timeBase.isRunning();
}
- BatchTimer(Uid uid, int type, ArrayList<Unpluggable> unpluggables,
- boolean inDischarge) {
- super(type, unpluggables);
+ BatchTimer(Uid uid, int type, TimeBase timeBase) {
+ super(type, timeBase);
mUid = uid;
- mInDischarge = inDischarge;
+ mInDischarge = timeBase.isRunning();
}
@Override
- public void writeToParcel(Parcel out, long batteryRealtime) {
- super.writeToParcel(out, batteryRealtime);
+ public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+ super.writeToParcel(out, elapsedRealtimeUs);
out.writeLong(mLastAddedTime);
out.writeLong(mLastAddedDuration);
}
@Override
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
recomputeLastDuration(SystemClock.elapsedRealtime() * 1000, false);
mInDischarge = false;
- super.plug(elapsedRealtime, batteryUptime, batteryRealtime);
+ super.onTimeStopped(elapsedRealtime, baseUptime, baseRealtime);
}
@Override
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
recomputeLastDuration(elapsedRealtime, false);
mInDischarge = true;
// If we are still within the last added duration, then re-added whatever remains.
if (mLastAddedTime == elapsedRealtime) {
mTotalTime += mLastAddedDuration;
}
- super.unplug(elapsedRealtime, batteryUptime, batteryRealtime);
+ super.onTimeStarted(elapsedRealtime, baseUptime, baseRealtime);
}
@Override
@@ -1060,11 +1292,11 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
- boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
+ boolean reset(boolean detachIfReset) {
final long now = SystemClock.elapsedRealtime() * 1000;
recomputeLastDuration(now, true);
boolean stillActive = mLastAddedTime == now;
- super.reset(stats, !stillActive && detachIfReset);
+ super.reset(!stillActive && detachIfReset);
return !stillActive;
}
}
@@ -1100,16 +1332,16 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mInList;
StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
- ArrayList<Unpluggable> unpluggables, Parcel in) {
- super(type, unpluggables, in);
+ TimeBase timeBase, Parcel in) {
+ super(type, timeBase, in);
mUid = uid;
mTimerPool = timerPool;
mUpdateTime = in.readLong();
}
StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
- ArrayList<Unpluggable> unpluggables) {
- super(type, unpluggables);
+ TimeBase timeBase) {
+ super(type, timeBase);
mUid = uid;
mTimerPool = timerPool;
}
@@ -1118,18 +1350,18 @@ public final class BatteryStatsImpl extends BatteryStats {
mTimeout = timeout;
}
- public void writeToParcel(Parcel out, long batteryRealtime) {
- super.writeToParcel(out, batteryRealtime);
+ public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+ super.writeToParcel(out, elapsedRealtimeUs);
out.writeLong(mUpdateTime);
}
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
if (mNesting > 0) {
if (DEBUG && mType < 0) {
Log.v(TAG, "old mUpdateTime=" + mUpdateTime);
}
- super.plug(elapsedRealtime, batteryUptime, batteryRealtime);
- mUpdateTime = batteryRealtime;
+ super.onTimeStopped(elapsedRealtime, baseUptime, baseRealtime);
+ mUpdateTime = baseRealtime;
if (DEBUG && mType < 0) {
Log.v(TAG, "new mUpdateTime=" + mUpdateTime);
}
@@ -1142,14 +1374,14 @@ public final class BatteryStatsImpl extends BatteryStats {
+ " mAcquireTime=" + mAcquireTime);
}
- void startRunningLocked(BatteryStatsImpl stats) {
+ void startRunningLocked(long elapsedRealtimeMs) {
if (mNesting++ == 0) {
- mUpdateTime = stats.getBatteryRealtimeLocked(
- SystemClock.elapsedRealtime() * 1000);
+ final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
+ mUpdateTime = batteryRealtime;
if (mTimerPool != null) {
// Accumulate time to all currently active timers before adding
// this new one to the pool.
- refreshTimersLocked(stats, mTimerPool);
+ refreshTimersLocked(batteryRealtime, mTimerPool, null);
// Add this timer to the active pool
mTimerPool.add(this);
}
@@ -1168,21 +1400,39 @@ public final class BatteryStatsImpl extends BatteryStats {
return mNesting > 0;
}
- void stopRunningLocked(BatteryStatsImpl stats) {
+ long checkpointRunningLocked(long elapsedRealtimeMs) {
+ if (mNesting > 0) {
+ // We are running...
+ final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
+ if (mTimerPool != null) {
+ return refreshTimersLocked(batteryRealtime, mTimerPool, this);
+ }
+ final long heldTime = batteryRealtime - mUpdateTime;
+ mUpdateTime = batteryRealtime;
+ mTotalTime += heldTime;
+ return heldTime;
+ }
+ return 0;
+ }
+
+ long getLastUpdateTimeMs() {
+ return mUpdateTime;
+ }
+
+ void stopRunningLocked(long elapsedRealtimeMs) {
// Ignore attempt to stop a timer that isn't running
if (mNesting == 0) {
return;
}
if (--mNesting == 0) {
+ final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
if (mTimerPool != null) {
// Accumulate time to all active counters, scaled by the total
// active in the pool, before taking this one out of the pool.
- refreshTimersLocked(stats, mTimerPool);
+ refreshTimersLocked(batteryRealtime, mTimerPool, null);
// Remove this timer from the active pool
mTimerPool.remove(this);
} else {
- final long realtime = SystemClock.elapsedRealtime() * 1000;
- final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime);
mNesting = 1;
mTotalTime = computeRunTimeLocked(batteryRealtime);
mNesting = 0;
@@ -1204,19 +1454,23 @@ public final class BatteryStatsImpl extends BatteryStats {
// Update the total time for all other running Timers with the same type as this Timer
// due to a change in timer count
- private static void refreshTimersLocked(final BatteryStatsImpl stats,
- final ArrayList<StopwatchTimer> pool) {
- final long realtime = SystemClock.elapsedRealtime() * 1000;
- final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime);
+ private static long refreshTimersLocked(long batteryRealtime,
+ final ArrayList<StopwatchTimer> pool, StopwatchTimer self) {
+ long selfTime = 0;
final int N = pool.size();
for (int i=N-1; i>= 0; i--) {
final StopwatchTimer t = pool.get(i);
long heldTime = batteryRealtime - t.mUpdateTime;
if (heldTime > 0) {
- t.mTotalTime += heldTime / N;
+ final long myTime = heldTime / N;
+ if (t == self) {
+ selfTime = myTime;
+ }
+ t.mTotalTime += myTime;
}
t.mUpdateTime = batteryRealtime;
}
+ return selfTime;
}
@Override
@@ -1235,12 +1489,11 @@ public final class BatteryStatsImpl extends BatteryStats {
return mCount;
}
- boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
+ boolean reset(boolean detachIfReset) {
boolean canDetach = mNesting <= 0;
- super.reset(stats, canDetach && detachIfReset);
+ super.reset(canDetach && detachIfReset);
if (mNesting > 0) {
- mUpdateTime = stats.getBatteryRealtimeLocked(
- SystemClock.elapsedRealtime() * 1000);
+ mUpdateTime = mTimeBase.getRealtime(SystemClock.elapsedRealtime() * 1000);
}
mAcquireTime = mTotalTime;
return canDetach;
@@ -1259,6 +1512,19 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ /*
+ * Get the wakeup reason counter, and create a new one if one
+ * doesn't already exist.
+ */
+ public LongSamplingCounter getWakeupReasonCounterLocked(String name) {
+ LongSamplingCounter counter = mWakeupReasonStats.get(name);
+ if (counter == null) {
+ counter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase);
+ mWakeupReasonStats.put(name, counter);
+ }
+ return counter;
+ }
+
private final Map<String, KernelWakelockStats> readKernelWakelockStats() {
FileInputStream is;
@@ -1403,51 +1669,12 @@ public final class BatteryStatsImpl extends BatteryStats {
public SamplingTimer getKernelWakelockTimerLocked(String name) {
SamplingTimer kwlt = mKernelWakelockStats.get(name);
if (kwlt == null) {
- kwlt = new SamplingTimer(mUnpluggables, mOnBatteryInternal,
- true /* track reported values */);
+ kwlt = new SamplingTimer(mOnBatteryScreenOffTimeBase, true /* track reported values */);
mKernelWakelockStats.put(name, kwlt);
}
return kwlt;
}
- /**
- * Radio uptime in microseconds when transferring data. This value is very approximate.
- * @return
- */
- private long getCurrentRadioDataUptime() {
- try {
- File awakeTimeFile = new File("/sys/devices/virtual/net/rmnet0/awake_time_ms");
- if (!awakeTimeFile.exists()) return 0;
- BufferedReader br = new BufferedReader(new FileReader(awakeTimeFile));
- String line = br.readLine();
- br.close();
- return Long.parseLong(line) * 1000;
- } catch (NumberFormatException nfe) {
- // Nothing
- } catch (IOException ioe) {
- // Nothing
- }
- return 0;
- }
-
- /**
- * @deprecated use getRadioDataUptime
- */
- public long getRadioDataUptimeMs() {
- return getRadioDataUptime() / 1000;
- }
-
- /**
- * Returns the duration that the cell radio was up for data transfers.
- */
- public long getRadioDataUptime() {
- if (mRadioDataStart == -1) {
- return mRadioDataUptime;
- } else {
- return getCurrentRadioDataUptime() - mRadioDataStart;
- }
- }
-
private int getCurrentBluetoothPingCount() {
if (mBtHeadset != null) {
List<BluetoothDevice> deviceList = mBtHeadset.getConnectedDevices();
@@ -1474,83 +1701,434 @@ public final class BatteryStatsImpl extends BatteryStats {
mBtHeadset = headset;
}
- int mChangedBufferStates = 0;
+ private int writeHistoryTag(HistoryTag tag) {
+ Integer idxObj = mHistoryTagPool.get(tag);
+ int idx;
+ if (idxObj != null) {
+ idx = idxObj;
+ } else {
+ idx = mNextHistoryTagIdx;
+ HistoryTag key = new HistoryTag();
+ key.setTo(tag);
+ tag.poolIdx = idx;
+ mHistoryTagPool.put(key, idx);
+ mNextHistoryTagIdx++;
+ mNumHistoryTagChars += key.string.length() + 1;
+ }
+ return idx;
+ }
+
+ private void readHistoryTag(int index, HistoryTag tag) {
+ tag.string = mReadHistoryStrings[index];
+ tag.uid = mReadHistoryUids[index];
+ tag.poolIdx = index;
+ }
+
+ // Part of initial delta int that specifies the time delta.
+ static final int DELTA_TIME_MASK = 0x7ffff;
+ static final int DELTA_TIME_LONG = 0x7ffff; // The delta is a following long
+ static final int DELTA_TIME_INT = 0x7fffe; // The delta is a following int
+ static final int DELTA_TIME_ABS = 0x7fffd; // Following is an entire abs update.
+ // Flag in delta int: a new battery level int follows.
+ static final int DELTA_BATTERY_LEVEL_FLAG = 0x00080000;
+ // Flag in delta int: a new full state and battery status int follows.
+ static final int DELTA_STATE_FLAG = 0x00100000;
+ // Flag in delta int: a new full state2 int follows.
+ static final int DELTA_STATE2_FLAG = 0x00200000;
+ // Flag in delta int: contains a wakelock or wakeReason tag.
+ static final int DELTA_WAKELOCK_FLAG = 0x00400000;
+ // Flag in delta int: contains an event description.
+ static final int DELTA_EVENT_FLAG = 0x00800000;
+ // These upper bits are the frequently changing state bits.
+ static final int DELTA_STATE_MASK = 0xff000000;
+
+ // These are the pieces of battery state that are packed in to the upper bits of
+ // the state int that have been packed in to the first delta int. They must fit
+ // in DELTA_STATE_MASK.
+ static final int STATE_BATTERY_STATUS_MASK = 0x00000007;
+ static final int STATE_BATTERY_STATUS_SHIFT = 29;
+ static final int STATE_BATTERY_HEALTH_MASK = 0x00000007;
+ static final int STATE_BATTERY_HEALTH_SHIFT = 26;
+ static final int STATE_BATTERY_PLUG_MASK = 0x00000003;
+ static final int STATE_BATTERY_PLUG_SHIFT = 24;
+
+ public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
+ if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
+ dest.writeInt(DELTA_TIME_ABS);
+ cur.writeToParcel(dest, 0);
+ return;
+ }
+
+ final long deltaTime = cur.time - last.time;
+ final int lastBatteryLevelInt = buildBatteryLevelInt(last);
+ final int lastStateInt = buildStateInt(last);
+
+ int deltaTimeToken;
+ if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) {
+ deltaTimeToken = DELTA_TIME_LONG;
+ } else if (deltaTime >= DELTA_TIME_ABS) {
+ deltaTimeToken = DELTA_TIME_INT;
+ } else {
+ deltaTimeToken = (int)deltaTime;
+ }
+ int firstToken = deltaTimeToken | (cur.states&DELTA_STATE_MASK);
+ final int batteryLevelInt = buildBatteryLevelInt(cur);
+ final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
+ if (batteryLevelIntChanged) {
+ firstToken |= DELTA_BATTERY_LEVEL_FLAG;
+ }
+ final int stateInt = buildStateInt(cur);
+ final boolean stateIntChanged = stateInt != lastStateInt;
+ if (stateIntChanged) {
+ firstToken |= DELTA_STATE_FLAG;
+ }
+ final boolean state2IntChanged = cur.states2 != last.states2;
+ if (state2IntChanged) {
+ firstToken |= DELTA_STATE2_FLAG;
+ }
+ if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
+ firstToken |= DELTA_WAKELOCK_FLAG;
+ }
+ if (cur.eventCode != HistoryItem.EVENT_NONE) {
+ firstToken |= DELTA_EVENT_FLAG;
+ }
+ dest.writeInt(firstToken);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+ + " deltaTime=" + deltaTime);
+
+ if (deltaTimeToken >= DELTA_TIME_INT) {
+ if (deltaTimeToken == DELTA_TIME_INT) {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int)deltaTime);
+ dest.writeInt((int)deltaTime);
+ } else {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime);
+ dest.writeLong(deltaTime);
+ }
+ }
+ if (batteryLevelIntChanged) {
+ dest.writeInt(batteryLevelInt);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
+ + Integer.toHexString(batteryLevelInt)
+ + " batteryLevel=" + cur.batteryLevel
+ + " batteryTemp=" + cur.batteryTemperature
+ + " batteryVolt=" + (int)cur.batteryVoltage);
+ }
+ if (stateIntChanged) {
+ dest.writeInt(stateInt);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: stateToken=0x"
+ + Integer.toHexString(stateInt)
+ + " batteryStatus=" + cur.batteryStatus
+ + " batteryHealth=" + cur.batteryHealth
+ + " batteryPlugType=" + cur.batteryPlugType
+ + " states=0x" + Integer.toHexString(cur.states));
+ }
+ if (state2IntChanged) {
+ dest.writeInt(cur.states2);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: states2=0x"
+ + Integer.toHexString(cur.states2));
+ }
+ if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
+ int wakeLockIndex;
+ int wakeReasonIndex;
+ if (cur.wakelockTag != null) {
+ wakeLockIndex = writeHistoryTag(cur.wakelockTag);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
+ + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
+ } else {
+ wakeLockIndex = 0xffff;
+ }
+ if (cur.wakeReasonTag != null) {
+ wakeReasonIndex = writeHistoryTag(cur.wakeReasonTag);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx
+ + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string);
+ } else {
+ wakeReasonIndex = 0xffff;
+ }
+ dest.writeInt((wakeReasonIndex<<16) | wakeLockIndex);
+ }
+ if (cur.eventCode != HistoryItem.EVENT_NONE) {
+ int index = writeHistoryTag(cur.eventTag);
+ int codeAndIndex = (cur.eventCode&0xffff) | (index<<16);
+ dest.writeInt(codeAndIndex);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#"
+ + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
+ + cur.eventTag.string);
+ }
+ }
+
+ private int buildBatteryLevelInt(HistoryItem h) {
+ return ((((int)h.batteryLevel)<<25)&0xfe000000)
+ | ((((int)h.batteryTemperature)<<14)&0x01ffc000)
+ | (((int)h.batteryVoltage)&0x00003fff);
+ }
+
+ private int buildStateInt(HistoryItem h) {
+ int plugType = 0;
+ if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_AC) != 0) {
+ plugType = 1;
+ } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_USB) != 0) {
+ plugType = 2;
+ } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0) {
+ plugType = 3;
+ }
+ return ((h.batteryStatus&STATE_BATTERY_STATUS_MASK)<<STATE_BATTERY_STATUS_SHIFT)
+ | ((h.batteryHealth&STATE_BATTERY_HEALTH_MASK)<<STATE_BATTERY_HEALTH_SHIFT)
+ | ((plugType&STATE_BATTERY_PLUG_MASK)<<STATE_BATTERY_PLUG_SHIFT)
+ | (h.states&(~DELTA_STATE_MASK));
+ }
+
+ public void readHistoryDelta(Parcel src, HistoryItem cur) {
+ int firstToken = src.readInt();
+ int deltaTimeToken = firstToken&DELTA_TIME_MASK;
+ cur.cmd = HistoryItem.CMD_UPDATE;
+ cur.numReadInts = 1;
+ if (DEBUG) Slog.i(TAG, "READ DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+ + " deltaTimeToken=" + deltaTimeToken);
+
+ if (deltaTimeToken < DELTA_TIME_ABS) {
+ cur.time += deltaTimeToken;
+ } else if (deltaTimeToken == DELTA_TIME_ABS) {
+ cur.time = src.readLong();
+ cur.numReadInts += 2;
+ if (DEBUG) Slog.i(TAG, "READ DELTA: ABS time=" + cur.time);
+ cur.readFromParcel(src);
+ return;
+ } else if (deltaTimeToken == DELTA_TIME_INT) {
+ int delta = src.readInt();
+ cur.time += delta;
+ cur.numReadInts += 1;
+ if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + cur.time);
+ } else {
+ long delta = src.readLong();
+ if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + cur.time);
+ cur.time += delta;
+ cur.numReadInts += 2;
+ }
+
+ if ((firstToken&DELTA_BATTERY_LEVEL_FLAG) != 0) {
+ int batteryLevelInt = src.readInt();
+ cur.batteryLevel = (byte)((batteryLevelInt>>25)&0x7f);
+ cur.batteryTemperature = (short)((batteryLevelInt<<7)>>21);
+ cur.batteryVoltage = (char)(batteryLevelInt&0x3fff);
+ cur.numReadInts += 1;
+ if (DEBUG) Slog.i(TAG, "READ DELTA: batteryToken=0x"
+ + Integer.toHexString(batteryLevelInt)
+ + " batteryLevel=" + cur.batteryLevel
+ + " batteryTemp=" + cur.batteryTemperature
+ + " batteryVolt=" + (int)cur.batteryVoltage);
+ }
+
+ if ((firstToken&DELTA_STATE_FLAG) != 0) {
+ int stateInt = src.readInt();
+ cur.states = (firstToken&DELTA_STATE_MASK) | (stateInt&(~DELTA_STATE_MASK));
+ cur.batteryStatus = (byte)((stateInt>>STATE_BATTERY_STATUS_SHIFT)
+ & STATE_BATTERY_STATUS_MASK);
+ cur.batteryHealth = (byte)((stateInt>>STATE_BATTERY_HEALTH_SHIFT)
+ & STATE_BATTERY_HEALTH_MASK);
+ cur.batteryPlugType = (byte)((stateInt>>STATE_BATTERY_PLUG_SHIFT)
+ & STATE_BATTERY_PLUG_MASK);
+ switch (cur.batteryPlugType) {
+ case 1:
+ cur.batteryPlugType = BatteryManager.BATTERY_PLUGGED_AC;
+ break;
+ case 2:
+ cur.batteryPlugType = BatteryManager.BATTERY_PLUGGED_USB;
+ break;
+ case 3:
+ cur.batteryPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
+ break;
+ }
+ cur.numReadInts += 1;
+ if (DEBUG) Slog.i(TAG, "READ DELTA: stateToken=0x"
+ + Integer.toHexString(stateInt)
+ + " batteryStatus=" + cur.batteryStatus
+ + " batteryHealth=" + cur.batteryHealth
+ + " batteryPlugType=" + cur.batteryPlugType
+ + " states=0x" + Integer.toHexString(cur.states));
+ } else {
+ cur.states = (firstToken&DELTA_STATE_MASK) | (cur.states&(~DELTA_STATE_MASK));
+ }
+
+ if ((firstToken&DELTA_STATE2_FLAG) != 0) {
+ cur.states2 = src.readInt();
+ if (DEBUG) Slog.i(TAG, "READ DELTA: states2=0x"
+ + Integer.toHexString(cur.states2));
+ }
+
+ if ((firstToken&DELTA_WAKELOCK_FLAG) != 0) {
+ int indexes = src.readInt();
+ int wakeLockIndex = indexes&0xffff;
+ int wakeReasonIndex = (indexes>>16)&0xffff;
+ if (wakeLockIndex != 0xffff) {
+ cur.wakelockTag = cur.localWakelockTag;
+ readHistoryTag(wakeLockIndex, cur.wakelockTag);
+ if (DEBUG) Slog.i(TAG, "READ DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
+ + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
+ } else {
+ cur.wakelockTag = null;
+ }
+ if (wakeReasonIndex != 0xffff) {
+ cur.wakeReasonTag = cur.localWakeReasonTag;
+ readHistoryTag(wakeReasonIndex, cur.wakeReasonTag);
+ if (DEBUG) Slog.i(TAG, "READ DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx
+ + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string);
+ } else {
+ cur.wakeReasonTag = null;
+ }
+ cur.numReadInts += 1;
+ } else {
+ cur.wakelockTag = null;
+ cur.wakeReasonTag = null;
+ }
+
+ if ((firstToken&DELTA_EVENT_FLAG) != 0) {
+ cur.eventTag = cur.localEventTag;
+ final int codeAndIndex = src.readInt();
+ cur.eventCode = (codeAndIndex&0xffff);
+ final int index = ((codeAndIndex>>16)&0xffff);
+ readHistoryTag(index, cur.eventTag);
+ cur.numReadInts += 1;
+ if (DEBUG) Slog.i(TAG, "READ DELTA: event=" + cur.eventCode + " tag=#"
+ + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
+ + cur.eventTag.string);
+ } else {
+ cur.eventCode = HistoryItem.EVENT_NONE;
+ }
+ }
- void addHistoryBufferLocked(long curTime) {
+ void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
if (!mHaveBatteryLevel || !mRecordingHistory) {
return;
}
- final long timeDiff = (mHistoryBaseTime+curTime) - mHistoryLastWritten.time;
+ final long timeDiff = (mHistoryBaseTime+elapsedRealtimeMs) - mHistoryLastWritten.time;
+ final int diffStates = mHistoryLastWritten.states^cur.states;
+ final int diffStates2 = mHistoryLastWritten.states2^cur.states2;
+ final int lastDiffStates = mHistoryLastWritten.states^mHistoryLastLastWritten.states;
+ final int lastDiffStates2 = mHistoryLastWritten.states2^mHistoryLastLastWritten.states2;
+ if (DEBUG) Slog.i(TAG, "ADD: tdelta=" + timeDiff + " diff="
+ + Integer.toHexString(diffStates) + " lastDiff="
+ + Integer.toHexString(lastDiffStates) + " diff2="
+ + Integer.toHexString(diffStates2) + " lastDiff2="
+ + Integer.toHexString(lastDiffStates2));
if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
- && timeDiff < 2000
- && ((mHistoryLastWritten.states^mHistoryCur.states)&mChangedBufferStates) == 0) {
- // If the current is the same as the one before, then we no
- // longer need the entry.
+ && timeDiff < 1000 && (diffStates&lastDiffStates) == 0
+ && (diffStates2&lastDiffStates2) == 0
+ && (mHistoryLastWritten.wakelockTag == null || cur.wakelockTag == null)
+ && (mHistoryLastWritten.wakeReasonTag == null || cur.wakeReasonTag == null)
+ && (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE
+ || cur.eventCode == HistoryItem.EVENT_NONE)
+ && mHistoryLastWritten.batteryLevel == cur.batteryLevel
+ && mHistoryLastWritten.batteryStatus == cur.batteryStatus
+ && mHistoryLastWritten.batteryHealth == cur.batteryHealth
+ && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
+ && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
+ && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) {
+ // We can merge this new change in with the last one. Merging is
+ // allowed as long as only the states have changed, and within those states
+ // as long as no bit has changed both between now and the last entry, as
+ // well as the last entry and the one before it (so we capture any toggles).
+ if (DEBUG) Slog.i(TAG, "ADD: rewinding back to " + mHistoryBufferLastPos);
mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
mHistoryBufferLastPos = -1;
- if (mHistoryLastLastWritten.cmd == HistoryItem.CMD_UPDATE
- && timeDiff < 500 && mHistoryLastLastWritten.same(mHistoryCur)) {
- // If this results in us returning to the state written
- // prior to the last one, then we can just delete the last
- // written one and drop the new one. Nothing more to do.
- mHistoryLastWritten.setTo(mHistoryLastLastWritten);
- mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL;
- return;
+ elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTime;
+ // If the last written history had a wakelock tag, we need to retain it.
+ // Note that the condition above made sure that we aren't in a case where
+ // both it and the current history item have a wakelock tag.
+ if (mHistoryLastWritten.wakelockTag != null) {
+ cur.wakelockTag = cur.localWakelockTag;
+ cur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag);
+ }
+ // If the last written history had a wake reason tag, we need to retain it.
+ // Note that the condition above made sure that we aren't in a case where
+ // both it and the current history item have a wakelock tag.
+ if (mHistoryLastWritten.wakeReasonTag != null) {
+ cur.wakeReasonTag = cur.localWakeReasonTag;
+ cur.wakeReasonTag.setTo(mHistoryLastWritten.wakeReasonTag);
+ }
+ // If the last written history had an event, we need to retain it.
+ // Note that the condition above made sure that we aren't in a case where
+ // both it and the current history item have an event.
+ if (mHistoryLastWritten.eventCode != HistoryItem.EVENT_NONE) {
+ cur.eventCode = mHistoryLastWritten.eventCode;
+ cur.eventTag = cur.localEventTag;
+ cur.eventTag.setTo(mHistoryLastWritten.eventTag);
}
- mChangedBufferStates |= mHistoryLastWritten.states^mHistoryCur.states;
- curTime = mHistoryLastWritten.time - mHistoryBaseTime;
mHistoryLastWritten.setTo(mHistoryLastLastWritten);
- } else {
- mChangedBufferStates = 0;
}
final int dataSize = mHistoryBuffer.dataSize();
if (dataSize >= MAX_HISTORY_BUFFER) {
if (!mHistoryOverflow) {
mHistoryOverflow = true;
- addHistoryBufferLocked(curTime, HistoryItem.CMD_OVERFLOW);
+ addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur);
+ addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_OVERFLOW, cur);
+ return;
}
// Once we've reached the maximum number of items, we only
// record changes to the battery level and the most interesting states.
// Once we've reached the maximum maximum number of items, we only
// record changes to the battery level.
- if (mHistoryLastWritten.batteryLevel == mHistoryCur.batteryLevel &&
+ if (mHistoryLastWritten.batteryLevel == cur.batteryLevel &&
(dataSize >= MAX_MAX_HISTORY_BUFFER
- || ((mHistoryLastWritten.states^mHistoryCur.states)
+ || ((mHistoryLastWritten.states^cur.states)
& HistoryItem.MOST_INTERESTING_STATES) == 0)) {
return;
}
+
+ addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur);
+ return;
}
- addHistoryBufferLocked(curTime, HistoryItem.CMD_UPDATE);
+ addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur);
}
- void addHistoryBufferLocked(long curTime, byte cmd) {
- int origPos = 0;
+ private void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, byte cmd,
+ HistoryItem cur) {
if (mIteratingHistory) {
- origPos = mHistoryBuffer.dataPosition();
- mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+ throw new IllegalStateException("Can't do this while iterating history!");
}
mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
mHistoryLastLastWritten.setTo(mHistoryLastWritten);
- mHistoryLastWritten.setTo(mHistoryBaseTime + curTime, cmd, mHistoryCur);
- mHistoryLastWritten.writeDelta(mHistoryBuffer, mHistoryLastLastWritten);
- mLastHistoryTime = curTime;
+ mHistoryLastWritten.setTo(mHistoryBaseTime + elapsedRealtimeMs, cmd, cur);
+ writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
+ mLastHistoryElapsedRealtime = elapsedRealtimeMs;
+ cur.wakelockTag = null;
+ cur.wakeReasonTag = null;
+ cur.eventCode = HistoryItem.EVENT_NONE;
+ cur.eventTag = null;
if (DEBUG_HISTORY) Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
+ " now " + mHistoryBuffer.dataPosition()
+ " size is now " + mHistoryBuffer.dataSize());
- if (mIteratingHistory) {
- mHistoryBuffer.setDataPosition(origPos);
- }
}
int mChangedStates = 0;
+ int mChangedStates2 = 0;
- void addHistoryRecordLocked(long curTime) {
- addHistoryBufferLocked(curTime);
+ void addHistoryRecordLocked(long elapsedRealtimeMs, long uptimeMs) {
+ if (mTrackRunningHistoryElapsedRealtime != 0) {
+ final long diffElapsed = elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtime;
+ final long diffUptime = uptimeMs - mTrackRunningHistoryUptime;
+ if (diffUptime < (diffElapsed-20)) {
+ final long wakeElapsedTime = elapsedRealtimeMs - (diffElapsed - diffUptime);
+ mHistoryAddTmp.setTo(mHistoryLastWritten);
+ mHistoryAddTmp.wakelockTag = null;
+ mHistoryAddTmp.wakeReasonTag = null;
+ mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE;
+ mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG;
+ addHistoryRecordInnerLocked(wakeElapsedTime, uptimeMs, mHistoryAddTmp);
+ }
+ }
+ mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG;
+ mTrackRunningHistoryElapsedRealtime = elapsedRealtimeMs;
+ mTrackRunningHistoryUptime = uptimeMs;
+ addHistoryRecordInnerLocked(elapsedRealtimeMs, uptimeMs, mHistoryCur);
+ }
+
+ void addHistoryRecordInnerLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
+ addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, cur);
if (!USE_OLD_HISTORY) {
return;
@@ -1565,30 +2143,33 @@ public final class BatteryStatsImpl extends BatteryStats {
// are now resetting back to their original value, then just collapse
// into one record.
if (mHistoryEnd != null && mHistoryEnd.cmd == HistoryItem.CMD_UPDATE
- && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+2000)
- && ((mHistoryEnd.states^mHistoryCur.states)&mChangedStates) == 0) {
+ && (mHistoryBaseTime+elapsedRealtimeMs) < (mHistoryEnd.time+1000)
+ && ((mHistoryEnd.states^cur.states)&mChangedStates) == 0
+ && ((mHistoryEnd.states2^cur.states2)&mChangedStates2) == 0) {
// If the current is the same as the one before, then we no
// longer need the entry.
if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE
- && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+500)
- && mHistoryLastEnd.same(mHistoryCur)) {
+ && (mHistoryBaseTime+elapsedRealtimeMs) < (mHistoryEnd.time+500)
+ && mHistoryLastEnd.sameNonEvent(cur)) {
mHistoryLastEnd.next = null;
mHistoryEnd.next = mHistoryCache;
mHistoryCache = mHistoryEnd;
mHistoryEnd = mHistoryLastEnd;
mHistoryLastEnd = null;
} else {
- mChangedStates |= mHistoryEnd.states^mHistoryCur.states;
- mHistoryEnd.setTo(mHistoryEnd.time, HistoryItem.CMD_UPDATE, mHistoryCur);
+ mChangedStates |= mHistoryEnd.states^cur.states;
+ mChangedStates2 |= mHistoryEnd.states^cur.states2;
+ mHistoryEnd.setTo(mHistoryEnd.time, HistoryItem.CMD_UPDATE, cur);
}
return;
}
mChangedStates = 0;
+ mChangedStates2 = 0;
if (mNumHistoryItems == MAX_HISTORY_ITEMS
|| mNumHistoryItems == MAX_MAX_HISTORY_ITEMS) {
- addHistoryRecordLocked(curTime, HistoryItem.CMD_OVERFLOW);
+ addHistoryRecordLocked(elapsedRealtimeMs, HistoryItem.CMD_OVERFLOW);
}
if (mNumHistoryItems >= MAX_HISTORY_ITEMS) {
@@ -1597,25 +2178,34 @@ public final class BatteryStatsImpl extends BatteryStats {
// Once we've reached the maximum maximum number of items, we only
// record changes to the battery level.
if (mHistoryEnd != null && mHistoryEnd.batteryLevel
- == mHistoryCur.batteryLevel &&
+ == cur.batteryLevel &&
(mNumHistoryItems >= MAX_MAX_HISTORY_ITEMS
- || ((mHistoryEnd.states^mHistoryCur.states)
+ || ((mHistoryEnd.states^cur.states)
& HistoryItem.MOST_INTERESTING_STATES) == 0)) {
return;
}
}
- addHistoryRecordLocked(curTime, HistoryItem.CMD_UPDATE);
+ addHistoryRecordLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE);
}
- void addHistoryRecordLocked(long curTime, byte cmd) {
+ void addHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code,
+ String name, int uid) {
+ mHistoryCur.eventCode = code;
+ mHistoryCur.eventTag = mHistoryCur.localEventTag;
+ mHistoryCur.eventTag.string = name;
+ mHistoryCur.eventTag.uid = uid;
+ addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ }
+
+ void addHistoryRecordLocked(long elapsedRealtimeMs, long uptimeMs, byte cmd, HistoryItem cur) {
HistoryItem rec = mHistoryCache;
if (rec != null) {
mHistoryCache = rec.next;
} else {
rec = new HistoryItem();
}
- rec.setTo(mHistoryBaseTime + curTime, cmd, mHistoryCur);
+ rec.setTo(mHistoryBaseTime + elapsedRealtimeMs, cmd, cur);
addHistoryRecordLocked(rec);
}
@@ -1644,114 +2234,252 @@ public final class BatteryStatsImpl extends BatteryStats {
}
mHistoryBaseTime = 0;
- mLastHistoryTime = 0;
+ mLastHistoryElapsedRealtime = 0;
+ mTrackRunningHistoryElapsedRealtime = 0;
+ mTrackRunningHistoryUptime = 0;
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
- mHistoryBuffer.setDataCapacity(MAX_HISTORY_BUFFER/2);
- mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL;
- mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+ mHistoryBuffer.setDataCapacity(MAX_HISTORY_BUFFER / 2);
+ mHistoryLastLastWritten.clear();
+ mHistoryLastWritten.clear();
+ mHistoryTagPool.clear();
+ mNextHistoryTagIdx = 0;
+ mNumHistoryTagChars = 0;
mHistoryBufferLastPos = -1;
mHistoryOverflow = false;
}
- public void doUnplugLocked(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
- for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
- mUnpluggables.get(i).unplug(elapsedRealtime, batteryUptime, batteryRealtime);
+ public void updateTimeBasesLocked(boolean unplugged, boolean screenOff, long uptime,
+ long realtime) {
+ if (mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime)) {
+ if (unplugged) {
+ // Track bt headset ping count
+ mBluetoothPingStart = getCurrentBluetoothPingCount();
+ mBluetoothPingCount = 0;
+ } else {
+ // Track bt headset ping count
+ mBluetoothPingCount = getBluetoothPingCount();
+ mBluetoothPingStart = -1;
+ }
}
- // Track radio awake time
- mRadioDataStart = getCurrentRadioDataUptime();
- mRadioDataUptime = 0;
+ boolean unpluggedScreenOff = unplugged && screenOff;
+ if (unpluggedScreenOff != mOnBatteryScreenOffTimeBase.isRunning()) {
+ updateKernelWakelocksLocked();
+ requestWakelockCpuUpdate();
+ if (!unpluggedScreenOff) {
+ // We are switching to no longer tracking wake locks, but we want
+ // the next CPU update we receive to take them in to account.
+ mDistributeWakelockCpu = true;
+ }
+ mOnBatteryScreenOffTimeBase.setRunning(unpluggedScreenOff, uptime, realtime);
+ }
+ }
- // Track bt headset ping count
- mBluetoothPingStart = getCurrentBluetoothPingCount();
- mBluetoothPingCount = 0;
+ public void addIsolatedUidLocked(int isolatedUid, int appUid) {
+ mIsolatedUids.put(isolatedUid, appUid);
}
- public void doPlugLocked(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
- for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
- mUnpluggables.get(i).plug(elapsedRealtime, batteryUptime, batteryRealtime);
+ public void removeIsolatedUidLocked(int isolatedUid, int appUid) {
+ int curUid = mIsolatedUids.get(isolatedUid, -1);
+ if (curUid == appUid) {
+ mIsolatedUids.delete(isolatedUid);
}
+ }
- // Track radio awake time
- mRadioDataUptime = getRadioDataUptime();
- mRadioDataStart = -1;
+ public int mapUid(int uid) {
+ int isolated = mIsolatedUids.get(uid, -1);
+ return isolated > 0 ? isolated : uid;
+ }
- // Track bt headset ping count
- mBluetoothPingCount = getBluetoothPingCount();
- mBluetoothPingStart = -1;
+ public void noteEventLocked(int code, String name, int uid) {
+ uid = mapUid(uid);
+ if ((code&HistoryItem.EVENT_FLAG_START) != 0) {
+ int idx = code&~HistoryItem.EVENT_FLAG_START;
+ HashMap<String, SparseBooleanArray> active = mActiveEvents[idx];
+ if (active == null) {
+ active = new HashMap<String, SparseBooleanArray>();
+ mActiveEvents[idx] = active;
+ }
+ SparseBooleanArray uids = active.get(name);
+ if (uids == null) {
+ uids = new SparseBooleanArray();
+ active.put(name, uids);
+ }
+ if (uids.get(uid)) {
+ // Already set, nothing to do!
+ return;
+ }
+ uids.put(uid, true);
+ } else if ((code&HistoryItem.EVENT_FLAG_FINISH) != 0) {
+ int idx = code&~HistoryItem.EVENT_FLAG_FINISH;
+ HashMap<String, SparseBooleanArray> active = mActiveEvents[idx];
+ if (active == null) {
+ // not currently active, nothing to do.
+ return;
+ }
+ SparseBooleanArray uids = active.get(name);
+ if (uids == null) {
+ // not currently active, nothing to do.
+ return;
+ }
+ idx = uids.indexOfKey(uid);
+ if (idx < 0 || !uids.valueAt(idx)) {
+ // not currently active, nothing to do.
+ return;
+ }
+ uids.removeAt(idx);
+ if (uids.size() <= 0) {
+ active.remove(name);
+ }
+ }
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ addHistoryEventLocked(elapsedRealtime, uptime, code, name, uid);
}
- int mWakeLockNesting;
+ private void requestWakelockCpuUpdate() {
+ if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) {
+ Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS);
+ mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS);
+ }
+ }
- public void noteStartWakeLocked(int uid, int pid, String name, int type) {
+ public void noteStartWakeLocked(int uid, int pid, String name, String historyName, int type,
+ boolean unimportantForLogging, long elapsedRealtime, long uptime) {
+ uid = mapUid(uid);
if (type == WAKE_TYPE_PARTIAL) {
// Only care about partial wake locks, since full wake locks
// will be canceled when the user puts the screen to sleep.
+ aggregateLastWakeupUptimeLocked(uptime);
if (mWakeLockNesting == 0) {
mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+ mHistoryCur.wakelockTag.string = historyName != null ? historyName : name;
+ mHistoryCur.wakelockTag.uid = uid;
+ mWakeLockImportant = !unimportantForLogging;
+ addHistoryRecordLocked(elapsedRealtime, uptime);
+ } else if (!mWakeLockImportant && !unimportantForLogging) {
+ if (mHistoryLastWritten.wakelockTag != null) {
+ // We'll try to update the last tag.
+ mHistoryLastWritten.wakelockTag = null;
+ mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+ mHistoryCur.wakelockTag.string = historyName != null ? historyName : name;
+ mHistoryCur.wakelockTag.uid = uid;
+ addHistoryRecordLocked(elapsedRealtime, uptime);
+ }
+ mWakeLockImportant = true;
}
mWakeLockNesting++;
}
if (uid >= 0) {
- if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) {
- Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS);
- mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS);
- }
- getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type);
+ //if (uid == 0) {
+ // Slog.wtf(TAG, "Acquiring wake lock from root: " + name);
+ //}
+ requestWakelockCpuUpdate();
+ getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type, elapsedRealtime);
}
}
- public void noteStopWakeLocked(int uid, int pid, String name, int type) {
+ public void noteStopWakeLocked(int uid, int pid, String name, int type, long elapsedRealtime,
+ long uptime) {
+ uid = mapUid(uid);
if (type == WAKE_TYPE_PARTIAL) {
mWakeLockNesting--;
if (mWakeLockNesting == 0) {
mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
}
}
if (uid >= 0) {
- if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) {
- Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS);
- mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS);
- }
- getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type);
+ requestWakelockCpuUpdate();
+ getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type, elapsedRealtime);
}
}
- public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) {
- int N = ws.size();
+ public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name,
+ String historyName, int type, boolean unimportantForLogging) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ final int N = ws.size();
for (int i=0; i<N; i++) {
- noteStartWakeLocked(ws.get(i), pid, name, type);
+ noteStartWakeLocked(ws.get(i), pid, name, historyName, type, unimportantForLogging,
+ elapsedRealtime, uptime);
+ }
+ }
+
+ public void noteChangeWakelockFromSourceLocked(WorkSource ws, int pid, String name, int type,
+ WorkSource newWs, int newPid, String newName,
+ String newHistoryName, int newType, boolean newUnimportantForLogging) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ // For correct semantics, we start the need worksources first, so that we won't
+ // make inappropriate history items as if all wake locks went away and new ones
+ // appeared. This is okay because tracking of wake locks allows nesting.
+ final int NN = newWs.size();
+ for (int i=0; i<NN; i++) {
+ noteStartWakeLocked(newWs.get(i), newPid, newName, newHistoryName, newType,
+ newUnimportantForLogging, elapsedRealtime, uptime);
+ }
+ final int NO = ws.size();
+ for (int i=0; i<NO; i++) {
+ noteStopWakeLocked(ws.get(i), pid, name, type, elapsedRealtime, uptime);
}
}
public void noteStopWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) {
- int N = ws.size();
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ final int N = ws.size();
for (int i=0; i<N; i++) {
- noteStopWakeLocked(ws.get(i), pid, name, type);
+ noteStopWakeLocked(ws.get(i), pid, name, type, elapsedRealtime, uptime);
+ }
+ }
+
+ void aggregateLastWakeupUptimeLocked(long uptimeMs) {
+ if (mLastWakeupReason != null) {
+ long deltaUptime = uptimeMs - mLastWakeupUptimeMs;
+ LongSamplingCounter timer = getWakeupReasonCounterLocked(mLastWakeupReason);
+ timer.addCountLocked(deltaUptime);
+ mLastWakeupReason = null;
}
}
+ public void noteWakeupReasonLocked(String reason) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ if (DEBUG_HISTORY) Slog.v(TAG, "Wakeup reason reason \"" + reason +"\": "
+ + Integer.toHexString(mHistoryCur.states));
+ aggregateLastWakeupUptimeLocked(uptime);
+ mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag;
+ mHistoryCur.wakeReasonTag.string = reason;
+ mHistoryCur.wakeReasonTag.uid = 0;
+ mLastWakeupReason = reason;
+ mLastWakeupUptimeMs = uptime;
+ addHistoryRecordLocked(elapsedRealtime, uptime);
+ }
+
public int startAddingCpuLocked() {
mHandler.removeMessages(MSG_UPDATE_WAKELOCKS);
- if (mScreenOn) {
- return 0;
- }
-
final int N = mPartialTimers.size();
if (N == 0) {
mLastPartialTimers.clear();
+ mDistributeWakelockCpu = false;
+ return 0;
+ }
+
+ if (!mOnBatteryScreenOffTimeBase.isRunning() && !mDistributeWakelockCpu) {
return 0;
}
+ mDistributeWakelockCpu = false;
+
// How many timers should consume CPU? Only want to include ones
// that have already been in the list.
for (int i=0; i<N; i++) {
@@ -1838,6 +2566,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void noteProcessDiedLocked(int uid, int pid) {
+ uid = mapUid(uid);
Uid u = mUidStats.get(uid);
if (u != null) {
u.mPids.remove(pid);
@@ -1845,17 +2574,19 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public long getProcessWakeTime(int uid, int pid, long realtime) {
+ uid = mapUid(uid);
Uid u = mUidStats.get(uid);
if (u != null) {
Uid.Pid p = u.mPids.get(pid);
if (p != null) {
- return p.mWakeSum + (p.mWakeStart != 0 ? (realtime - p.mWakeStart) : 0);
+ return p.mWakeSumMs + (p.mWakeNesting > 0 ? (realtime - p.mWakeStartMs) : 0);
}
}
return 0;
}
public void reportExcessiveWakeLocked(int uid, String proc, long overTime, long usedTime) {
+ uid = mapUid(uid);
Uid u = mUidStats.get(uid);
if (u != null) {
u.reportExcessiveWakeLocked(proc, overTime, usedTime);
@@ -1863,6 +2594,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void reportExcessiveCpuLocked(int uid, String proc, long overTime, long usedTime) {
+ uid = mapUid(uid);
Uid u = mUidStats.get(uid);
if (u != null) {
u.reportExcessiveCpuLocked(proc, overTime, usedTime);
@@ -1872,67 +2604,85 @@ public final class BatteryStatsImpl extends BatteryStats {
int mSensorNesting;
public void noteStartSensorLocked(int uid, int sensor) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
if (mSensorNesting == 0) {
mHistoryCur.states |= HistoryItem.STATE_SENSOR_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Start sensor to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
}
mSensorNesting++;
- getUidStatsLocked(uid).noteStartSensor(sensor);
+ getUidStatsLocked(uid).noteStartSensor(sensor, elapsedRealtime);
}
public void noteStopSensorLocked(int uid, int sensor) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mSensorNesting--;
if (mSensorNesting == 0) {
mHistoryCur.states &= ~HistoryItem.STATE_SENSOR_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Stop sensor to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
}
- getUidStatsLocked(uid).noteStopSensor(sensor);
+ getUidStatsLocked(uid).noteStopSensor(sensor, elapsedRealtime);
}
int mGpsNesting;
public void noteStartGpsLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
if (mGpsNesting == 0) {
mHistoryCur.states |= HistoryItem.STATE_GPS_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Start GPS to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
}
mGpsNesting++;
- getUidStatsLocked(uid).noteStartGps();
+ getUidStatsLocked(uid).noteStartGps(elapsedRealtime);
}
public void noteStopGpsLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mGpsNesting--;
if (mGpsNesting == 0) {
mHistoryCur.states &= ~HistoryItem.STATE_GPS_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Stop GPS to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
}
- getUidStatsLocked(uid).noteStopGps();
+ getUidStatsLocked(uid).noteStopGps(elapsedRealtime);
}
public void noteScreenOnLocked() {
if (!mScreenOn) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
mScreenOn = true;
- mScreenOnTimer.startRunningLocked(this);
+ mScreenOnTimer.startRunningLocked(elapsedRealtime);
if (mScreenBrightnessBin >= 0) {
- mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(this);
+ mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(elapsedRealtime);
}
+ updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), false,
+ SystemClock.uptimeMillis() * 1000, elapsedRealtime * 1000);
+
// Fake a wake lock, so we consider the device waked as long
// as the screen is on.
- noteStartWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL);
-
+ noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false,
+ elapsedRealtime, uptime);
+
// Update discharge amounts.
if (mOnBatteryInternal) {
updateDischargeScreenLevelsLocked(false, true);
@@ -1942,18 +2692,24 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteScreenOffLocked() {
if (mScreenOn) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
mScreenOn = false;
- mScreenOnTimer.stopRunningLocked(this);
+ mScreenOnTimer.stopRunningLocked(elapsedRealtime);
if (mScreenBrightnessBin >= 0) {
- mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(this);
+ mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(elapsedRealtime);
}
- noteStopWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL);
-
+ noteStopWakeLocked(-1, -1, "screen", WAKE_TYPE_PARTIAL,
+ elapsedRealtime, uptime);
+
+ updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), true,
+ SystemClock.uptimeMillis() * 1000, elapsedRealtime * 1000);
+
// Update discharge amounts.
if (mOnBatteryInternal) {
updateDischargeScreenLevelsLocked(true, false);
@@ -1967,16 +2723,18 @@ public final class BatteryStatsImpl extends BatteryStats {
if (bin < 0) bin = 0;
else if (bin >= NUM_SCREEN_BRIGHTNESS_BINS) bin = NUM_SCREEN_BRIGHTNESS_BINS-1;
if (mScreenBrightnessBin != bin) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_BRIGHTNESS_MASK)
| (bin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
if (DEBUG_HISTORY) Slog.v(TAG, "Screen brightness " + bin + " to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
if (mScreenOn) {
if (mScreenBrightnessBin >= 0) {
- mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(this);
+ mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(elapsedRealtime);
}
- mScreenBrightnessTimer[bin].startRunningLocked(this);
+ mScreenBrightnessTimer[bin].startRunningLocked(elapsedRealtime);
}
mScreenBrightnessBin = bin;
}
@@ -1987,38 +2745,85 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void noteUserActivityLocked(int uid, int event) {
- getUidStatsLocked(uid).noteUserActivityLocked(event);
+ if (mOnBatteryInternal) {
+ uid = mapUid(uid);
+ getUidStatsLocked(uid).noteUserActivityLocked(event);
+ }
+ }
+
+ public void noteMobileRadioPowerState(int powerState, long timestampNs) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ if (mMobileRadioPowerState != powerState) {
+ long realElapsedRealtimeMs;
+ final boolean active =
+ powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM
+ || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
+ if (active) {
+ realElapsedRealtimeMs = elapsedRealtime;
+ mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
+ } else {
+ realElapsedRealtimeMs = timestampNs / (1000*1000);
+ long lastUpdateTimeMs = mMobileRadioActiveTimer.getLastUpdateTimeMs();
+ if (realElapsedRealtimeMs < lastUpdateTimeMs) {
+ Slog.wtf(TAG, "Data connection inactive timestamp " + realElapsedRealtimeMs
+ + " is before start time " + lastUpdateTimeMs);
+ realElapsedRealtimeMs = elapsedRealtime;
+ } else if (realElapsedRealtimeMs < elapsedRealtime) {
+ mMobileRadioActiveAdjustedTime.addCountLocked(elapsedRealtime
+ - realElapsedRealtimeMs);
+ }
+ mHistoryCur.states &= ~HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
+ }
+ if (DEBUG_HISTORY) Slog.v(TAG, "Mobile network active " + active + " to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(elapsedRealtime, uptime);
+ mMobileRadioPowerState = powerState;
+ if (active) {
+ mMobileRadioActiveTimer.startRunningLocked(elapsedRealtime);
+ mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtime);
+ } else {
+ mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs);
+ updateNetworkActivityLocked(NET_UPDATE_MOBILE, realElapsedRealtimeMs);
+ mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs);
+ }
+ }
}
public void notePhoneOnLocked() {
if (!mPhoneOn) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mHistoryCur.states |= HistoryItem.STATE_PHONE_IN_CALL_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Phone on to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
mPhoneOn = true;
- mPhoneOnTimer.startRunningLocked(this);
+ mPhoneOnTimer.startRunningLocked(elapsedRealtime);
}
}
public void notePhoneOffLocked() {
if (mPhoneOn) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mHistoryCur.states &= ~HistoryItem.STATE_PHONE_IN_CALL_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Phone off to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
mPhoneOn = false;
- mPhoneOnTimer.stopRunningLocked(this);
+ mPhoneOnTimer.stopRunningLocked(elapsedRealtime);
}
}
void stopAllSignalStrengthTimersLocked(int except) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
if (i == except) {
continue;
}
while (mPhoneSignalStrengthsTimer[i].isRunningLocked()) {
- mPhoneSignalStrengthsTimer[i].stopRunningLocked(this);
+ mPhoneSignalStrengthsTimer[i].stopRunningLocked(elapsedRealtime);
}
}
}
@@ -2036,26 +2841,29 @@ public final class BatteryStatsImpl extends BatteryStats {
return state;
}
- private void updateAllPhoneStateLocked(int state, int simState, int bin) {
+ private void updateAllPhoneStateLocked(int state, int simState, int strengthBin) {
boolean scanning = false;
boolean newHistory = false;
mPhoneServiceStateRaw = state;
mPhoneSimStateRaw = simState;
- mPhoneSignalStrengthBinRaw = bin;
+ mPhoneSignalStrengthBinRaw = strengthBin;
+
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
if (simState == TelephonyManager.SIM_STATE_ABSENT) {
// In this case we will always be STATE_OUT_OF_SERVICE, so need
// to infer that we are scanning from other data.
if (state == ServiceState.STATE_OUT_OF_SERVICE
- && bin > SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+ && strengthBin > SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
state = ServiceState.STATE_IN_SERVICE;
}
}
// If the phone is powered off, stop all timers.
if (state == ServiceState.STATE_POWER_OFF) {
- bin = -1;
+ strengthBin = -1;
// If we are in service, make sure the correct signal string timer is running.
} else if (state == ServiceState.STATE_IN_SERVICE) {
@@ -2065,13 +2873,13 @@ public final class BatteryStatsImpl extends BatteryStats {
// bin and have the scanning bit set.
} else if (state == ServiceState.STATE_OUT_OF_SERVICE) {
scanning = true;
- bin = SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ strengthBin = SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
if (!mPhoneSignalScanningTimer.isRunningLocked()) {
mHistoryCur.states |= HistoryItem.STATE_PHONE_SCANNING_FLAG;
newHistory = true;
if (DEBUG_HISTORY) Slog.v(TAG, "Phone started scanning to: "
+ Integer.toHexString(mHistoryCur.states));
- mPhoneSignalScanningTimer.startRunningLocked(this);
+ mPhoneSignalScanningTimer.startRunningLocked(elapsedRealtime);
}
}
@@ -2082,7 +2890,7 @@ public final class BatteryStatsImpl extends BatteryStats {
if (DEBUG_HISTORY) Slog.v(TAG, "Phone stopped scanning to: "
+ Integer.toHexString(mHistoryCur.states));
newHistory = true;
- mPhoneSignalScanningTimer.stopRunningLocked(this);
+ mPhoneSignalScanningTimer.stopRunningLocked(elapsedRealtime);
}
}
@@ -2095,27 +2903,28 @@ public final class BatteryStatsImpl extends BatteryStats {
mPhoneServiceState = state;
}
- if (mPhoneSignalStrengthBin != bin) {
+ if (mPhoneSignalStrengthBin != strengthBin) {
if (mPhoneSignalStrengthBin >= 0) {
- mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].stopRunningLocked(this);
+ mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].stopRunningLocked(
+ elapsedRealtime);
}
- if (bin >= 0) {
- if (!mPhoneSignalStrengthsTimer[bin].isRunningLocked()) {
- mPhoneSignalStrengthsTimer[bin].startRunningLocked(this);
+ if (strengthBin >= 0) {
+ if (!mPhoneSignalStrengthsTimer[strengthBin].isRunningLocked()) {
+ mPhoneSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime);
}
mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_SIGNAL_STRENGTH_MASK)
- | (bin << HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + bin + " to: "
+ | (strengthBin << HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT);
+ if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: "
+ Integer.toHexString(mHistoryCur.states));
newHistory = true;
} else {
stopAllSignalStrengthTimersLocked(-1);
}
- mPhoneSignalStrengthBin = bin;
+ mPhoneSignalStrengthBin = strengthBin;
}
if (newHistory) {
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
}
}
@@ -2189,120 +2998,142 @@ public final class BatteryStatsImpl extends BatteryStats {
}
if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData);
if (mPhoneDataConnectionType != bin) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK)
| (bin << HistoryItem.STATE_DATA_CONNECTION_SHIFT);
if (DEBUG_HISTORY) Slog.v(TAG, "Data connection " + bin + " to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
if (mPhoneDataConnectionType >= 0) {
- mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(this);
+ mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(
+ elapsedRealtime);
}
mPhoneDataConnectionType = bin;
- mPhoneDataConnectionsTimer[bin].startRunningLocked(this);
+ mPhoneDataConnectionsTimer[bin].startRunningLocked(elapsedRealtime);
}
}
public void noteWifiOnLocked() {
if (!mWifiOn) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mHistoryCur.states |= HistoryItem.STATE_WIFI_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI on to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
mWifiOn = true;
- mWifiOnTimer.startRunningLocked(this);
+ mWifiOnTimer.startRunningLocked(elapsedRealtime);
}
}
public void noteWifiOffLocked() {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
if (mWifiOn) {
mHistoryCur.states &= ~HistoryItem.STATE_WIFI_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI off to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
mWifiOn = false;
- mWifiOnTimer.stopRunningLocked(this);
- }
- if (mWifiOnUid >= 0) {
- getUidStatsLocked(mWifiOnUid).noteWifiStoppedLocked();
- mWifiOnUid = -1;
+ mWifiOnTimer.stopRunningLocked(elapsedRealtime);
}
}
public void noteAudioOnLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
if (!mAudioOn) {
mHistoryCur.states |= HistoryItem.STATE_AUDIO_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Audio on to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
mAudioOn = true;
- mAudioOnTimer.startRunningLocked(this);
+ mAudioOnTimer.startRunningLocked(elapsedRealtime);
}
- getUidStatsLocked(uid).noteAudioTurnedOnLocked();
+ getUidStatsLocked(uid).noteAudioTurnedOnLocked(elapsedRealtime);
}
public void noteAudioOffLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
if (mAudioOn) {
mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
mAudioOn = false;
- mAudioOnTimer.stopRunningLocked(this);
+ mAudioOnTimer.stopRunningLocked(elapsedRealtime);
}
- getUidStatsLocked(uid).noteAudioTurnedOffLocked();
+ getUidStatsLocked(uid).noteAudioTurnedOffLocked(elapsedRealtime);
}
public void noteVideoOnLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
if (!mVideoOn) {
- mHistoryCur.states |= HistoryItem.STATE_VIDEO_ON_FLAG;
+ mHistoryCur.states2 |= HistoryItem.STATE2_VIDEO_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Video on to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
mVideoOn = true;
- mVideoOnTimer.startRunningLocked(this);
+ mVideoOnTimer.startRunningLocked(elapsedRealtime);
}
- getUidStatsLocked(uid).noteVideoTurnedOnLocked();
+ getUidStatsLocked(uid).noteVideoTurnedOnLocked(elapsedRealtime);
}
public void noteVideoOffLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
if (mVideoOn) {
- mHistoryCur.states &= ~HistoryItem.STATE_VIDEO_ON_FLAG;
+ mHistoryCur.states2 &= ~HistoryItem.STATE2_VIDEO_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
mVideoOn = false;
- mVideoOnTimer.stopRunningLocked(this);
+ mVideoOnTimer.stopRunningLocked(elapsedRealtime);
}
- getUidStatsLocked(uid).noteVideoTurnedOffLocked();
+ getUidStatsLocked(uid).noteVideoTurnedOffLocked(elapsedRealtime);
}
public void noteActivityResumedLocked(int uid) {
- getUidStatsLocked(uid).noteActivityResumedLocked();
+ uid = mapUid(uid);
+ getUidStatsLocked(uid).noteActivityResumedLocked(SystemClock.elapsedRealtime());
}
public void noteActivityPausedLocked(int uid) {
- getUidStatsLocked(uid).noteActivityPausedLocked();
+ uid = mapUid(uid);
+ getUidStatsLocked(uid).noteActivityPausedLocked(SystemClock.elapsedRealtime());
}
public void noteVibratorOnLocked(int uid, long durationMillis) {
+ uid = mapUid(uid);
getUidStatsLocked(uid).noteVibratorOnLocked(durationMillis);
}
public void noteVibratorOffLocked(int uid) {
+ uid = mapUid(uid);
getUidStatsLocked(uid).noteVibratorOffLocked();
}
public void noteWifiRunningLocked(WorkSource ws) {
if (!mGlobalWifiRunning) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mHistoryCur.states |= HistoryItem.STATE_WIFI_RUNNING_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI running to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
mGlobalWifiRunning = true;
- mGlobalWifiRunningTimer.startRunningLocked(this);
+ mGlobalWifiRunningTimer.startRunningLocked(elapsedRealtime);
int N = ws.size();
for (int i=0; i<N; i++) {
- getUidStatsLocked(ws.get(i)).noteWifiRunningLocked();
+ int uid = mapUid(ws.get(i));
+ getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime);
}
} else {
Log.w(TAG, "noteWifiRunningLocked -- called while WIFI running");
@@ -2311,13 +3142,16 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteWifiRunningChangedLocked(WorkSource oldWs, WorkSource newWs) {
if (mGlobalWifiRunning) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
int N = oldWs.size();
for (int i=0; i<N; i++) {
- getUidStatsLocked(oldWs.get(i)).noteWifiStoppedLocked();
+ int uid = mapUid(oldWs.get(i));
+ getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime);
}
N = newWs.size();
for (int i=0; i<N; i++) {
- getUidStatsLocked(newWs.get(i)).noteWifiRunningLocked();
+ int uid = mapUid(newWs.get(i));
+ getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime);
}
} else {
Log.w(TAG, "noteWifiRunningChangedLocked -- called while WIFI not running");
@@ -2326,121 +3160,174 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteWifiStoppedLocked(WorkSource ws) {
if (mGlobalWifiRunning) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RUNNING_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI stopped to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
mGlobalWifiRunning = false;
- mGlobalWifiRunningTimer.stopRunningLocked(this);
+ mGlobalWifiRunningTimer.stopRunningLocked(elapsedRealtime);
int N = ws.size();
for (int i=0; i<N; i++) {
- getUidStatsLocked(ws.get(i)).noteWifiStoppedLocked();
+ int uid = mapUid(ws.get(i));
+ getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime);
}
} else {
Log.w(TAG, "noteWifiStoppedLocked -- called while WIFI not running");
}
}
+ public void noteWifiStateLocked(int wifiState, String accessPoint) {
+ if (DEBUG) Log.i(TAG, "WiFi state -> " + wifiState);
+ if (mWifiState != wifiState) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ if (mWifiState >= 0) {
+ mWifiStateTimer[mWifiState].stopRunningLocked(elapsedRealtime);
+ }
+ mWifiState = wifiState;
+ mWifiStateTimer[wifiState].startRunningLocked(elapsedRealtime);
+ }
+ }
+
public void noteBluetoothOnLocked() {
if (!mBluetoothOn) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mHistoryCur.states |= HistoryItem.STATE_BLUETOOTH_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth on to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
mBluetoothOn = true;
- mBluetoothOnTimer.startRunningLocked(this);
+ mBluetoothOnTimer.startRunningLocked(elapsedRealtime);
}
}
public void noteBluetoothOffLocked() {
if (mBluetoothOn) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mHistoryCur.states &= ~HistoryItem.STATE_BLUETOOTH_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth off to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
mBluetoothOn = false;
- mBluetoothOnTimer.stopRunningLocked(this);
+ mBluetoothOnTimer.stopRunningLocked(elapsedRealtime);
+ }
+ }
+
+ public void noteBluetoothStateLocked(int bluetoothState) {
+ if (DEBUG) Log.i(TAG, "Bluetooth state -> " + bluetoothState);
+ if (mBluetoothState != bluetoothState) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ if (mBluetoothState >= 0) {
+ mBluetoothStateTimer[mBluetoothState].stopRunningLocked(elapsedRealtime);
+ }
+ mBluetoothState = bluetoothState;
+ mBluetoothStateTimer[bluetoothState].startRunningLocked(elapsedRealtime);
}
}
int mWifiFullLockNesting = 0;
public void noteFullWifiLockAcquiredLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
if (mWifiFullLockNesting == 0) {
mHistoryCur.states |= HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
}
mWifiFullLockNesting++;
- getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked();
+ getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked(elapsedRealtime);
}
public void noteFullWifiLockReleasedLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mWifiFullLockNesting--;
if (mWifiFullLockNesting == 0) {
mHistoryCur.states &= ~HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
}
- getUidStatsLocked(uid).noteFullWifiLockReleasedLocked();
+ getUidStatsLocked(uid).noteFullWifiLockReleasedLocked(elapsedRealtime);
}
int mWifiScanNesting = 0;
public void noteWifiScanStartedLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
if (mWifiScanNesting == 0) {
mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan started for: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
}
mWifiScanNesting++;
- getUidStatsLocked(uid).noteWifiScanStartedLocked();
+ getUidStatsLocked(uid).noteWifiScanStartedLocked(elapsedRealtime);
}
public void noteWifiScanStoppedLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mWifiScanNesting--;
if (mWifiScanNesting == 0) {
mHistoryCur.states &= ~HistoryItem.STATE_WIFI_SCAN_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan stopped for: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
}
- getUidStatsLocked(uid).noteWifiScanStoppedLocked();
+ getUidStatsLocked(uid).noteWifiScanStoppedLocked(elapsedRealtime);
}
public void noteWifiBatchedScanStartedLocked(int uid, int csph) {
- getUidStatsLocked(uid).noteWifiBatchedScanStartedLocked(csph);
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ getUidStatsLocked(uid).noteWifiBatchedScanStartedLocked(csph, elapsedRealtime);
}
public void noteWifiBatchedScanStoppedLocked(int uid) {
- getUidStatsLocked(uid).noteWifiBatchedScanStoppedLocked();
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ getUidStatsLocked(uid).noteWifiBatchedScanStoppedLocked(elapsedRealtime);
}
int mWifiMulticastNesting = 0;
public void noteWifiMulticastEnabledLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
if (mWifiMulticastNesting == 0) {
mHistoryCur.states |= HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
}
mWifiMulticastNesting++;
- getUidStatsLocked(uid).noteWifiMulticastEnabledLocked();
+ getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(elapsedRealtime);
}
public void noteWifiMulticastDisabledLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
mWifiMulticastNesting--;
if (mWifiMulticastNesting == 0) {
mHistoryCur.states &= ~HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
}
- getUidStatsLocked(uid).noteWifiMulticastDisabledLocked();
+ getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(elapsedRealtime);
}
public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) {
@@ -2499,16 +3386,45 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ private static String[] includeInStringArray(String[] array, String str) {
+ if (ArrayUtils.indexOf(array, str) >= 0) {
+ return array;
+ }
+ String[] newArray = new String[array.length+1];
+ System.arraycopy(array, 0, newArray, 0, array.length);
+ newArray[array.length] = str;
+ return newArray;
+ }
+
+ private static String[] excludeFromStringArray(String[] array, String str) {
+ int index = ArrayUtils.indexOf(array, str);
+ if (index >= 0) {
+ String[] newArray = new String[array.length-1];
+ if (index > 0) {
+ System.arraycopy(array, 0, newArray, 0, index);
+ }
+ if (index < array.length-1) {
+ System.arraycopy(array, index+1, newArray, index, array.length-index-1);
+ }
+ return newArray;
+ }
+ return array;
+ }
+
public void noteNetworkInterfaceTypeLocked(String iface, int networkType) {
if (ConnectivityManager.isNetworkTypeMobile(networkType)) {
- mMobileIfaces.add(iface);
+ mMobileIfaces = includeInStringArray(mMobileIfaces, iface);
+ if (DEBUG) Slog.d(TAG, "Note mobile iface " + iface + ": " + mMobileIfaces);
} else {
- mMobileIfaces.remove(iface);
+ mMobileIfaces = excludeFromStringArray(mMobileIfaces, iface);
+ if (DEBUG) Slog.d(TAG, "Note non-mobile iface " + iface + ": " + mMobileIfaces);
}
if (ConnectivityManager.isNetworkTypeWifi(networkType)) {
- mWifiIfaces.add(iface);
+ mWifiIfaces = includeInStringArray(mWifiIfaces, iface);
+ if (DEBUG) Slog.d(TAG, "Note wifi iface " + iface + ": " + mWifiIfaces);
} else {
- mWifiIfaces.remove(iface);
+ mWifiIfaces = excludeFromStringArray(mWifiIfaces, iface);
+ if (DEBUG) Slog.d(TAG, "Note non-wifi iface " + iface + ": " + mWifiIfaces);
}
}
@@ -2516,37 +3432,45 @@ public final class BatteryStatsImpl extends BatteryStats {
// During device boot, qtaguid isn't enabled until after the inital
// loading of battery stats. Now that they're enabled, take our initial
// snapshot for future delta calculation.
- updateNetworkActivityLocked();
+ updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime());
+ }
+
+ @Override public long getScreenOnTime(long elapsedRealtimeUs, int which) {
+ return mScreenOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
- @Override public long getScreenOnTime(long batteryRealtime, int which) {
- return mScreenOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ @Override public int getScreenOnCount(int which) {
+ return mScreenOnTimer.getCountLocked(which);
}
@Override public long getScreenBrightnessTime(int brightnessBin,
- long batteryRealtime, int which) {
+ long elapsedRealtimeUs, int which) {
return mScreenBrightnessTimer[brightnessBin].getTotalTimeLocked(
- batteryRealtime, which);
+ elapsedRealtimeUs, which);
}
@Override public int getInputEventCount(int which) {
return mInputEventCounter.getCountLocked(which);
}
- @Override public long getPhoneOnTime(long batteryRealtime, int which) {
- return mPhoneOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ @Override public long getPhoneOnTime(long elapsedRealtimeUs, int which) {
+ return mPhoneOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+ }
+
+ @Override public int getPhoneOnCount(int which) {
+ return mPhoneOnTimer.getCountLocked(which);
}
@Override public long getPhoneSignalStrengthTime(int strengthBin,
- long batteryRealtime, int which) {
+ long elapsedRealtimeUs, int which) {
return mPhoneSignalStrengthsTimer[strengthBin].getTotalTimeLocked(
- batteryRealtime, which);
+ elapsedRealtimeUs, which);
}
@Override public long getPhoneSignalScanningTime(
- long batteryRealtime, int which) {
+ long elapsedRealtimeUs, int which) {
return mPhoneSignalScanningTimer.getTotalTimeLocked(
- batteryRealtime, which);
+ elapsedRealtimeUs, which);
}
@Override public int getPhoneSignalStrengthCount(int strengthBin, int which) {
@@ -2554,36 +3478,89 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override public long getPhoneDataConnectionTime(int dataType,
- long batteryRealtime, int which) {
+ long elapsedRealtimeUs, int which) {
return mPhoneDataConnectionsTimer[dataType].getTotalTimeLocked(
- batteryRealtime, which);
+ elapsedRealtimeUs, which);
}
@Override public int getPhoneDataConnectionCount(int dataType, int which) {
return mPhoneDataConnectionsTimer[dataType].getCountLocked(which);
}
- @Override public long getWifiOnTime(long batteryRealtime, int which) {
- return mWifiOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ @Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) {
+ return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+ }
+
+ @Override public int getMobileRadioActiveCount(int which) {
+ return mMobileRadioActiveTimer.getCountLocked(which);
+ }
+
+ @Override public long getMobileRadioActiveAdjustedTime(int which) {
+ return mMobileRadioActiveAdjustedTime.getCountLocked(which);
+ }
+
+ @Override public long getMobileRadioActiveUnknownTime(int which) {
+ return mMobileRadioActiveUnknownTime.getCountLocked(which);
+ }
+
+ @Override public int getMobileRadioActiveUnknownCount(int which) {
+ return (int)mMobileRadioActiveUnknownCount.getCountLocked(which);
+ }
+
+ @Override public long getWifiOnTime(long elapsedRealtimeUs, int which) {
+ return mWifiOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+ }
+
+ @Override public long getGlobalWifiRunningTime(long elapsedRealtimeUs, int which) {
+ return mGlobalWifiRunningTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+ }
+
+ @Override public long getWifiStateTime(int wifiState,
+ long elapsedRealtimeUs, int which) {
+ return mWifiStateTimer[wifiState].getTotalTimeLocked(
+ elapsedRealtimeUs, which);
+ }
+
+ @Override public int getWifiStateCount(int wifiState, int which) {
+ return mWifiStateTimer[wifiState].getCountLocked(which);
}
- @Override public long getGlobalWifiRunningTime(long batteryRealtime, int which) {
- return mGlobalWifiRunningTimer.getTotalTimeLocked(batteryRealtime, which);
+ @Override public long getBluetoothOnTime(long elapsedRealtimeUs, int which) {
+ return mBluetoothOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
- @Override public long getBluetoothOnTime(long batteryRealtime, int which) {
- return mBluetoothOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ @Override public long getBluetoothStateTime(int bluetoothState,
+ long elapsedRealtimeUs, int which) {
+ return mBluetoothStateTimer[bluetoothState].getTotalTimeLocked(
+ elapsedRealtimeUs, which);
+ }
+
+ @Override public int getBluetoothStateCount(int bluetoothState, int which) {
+ return mBluetoothStateTimer[bluetoothState].getCountLocked(which);
}
@Override
- public long getNetworkActivityCount(int type, int which) {
- if (type >= 0 && type < mNetworkActivityCounters.length) {
- return mNetworkActivityCounters[type].getCountLocked(which);
+ public long getNetworkActivityBytes(int type, int which) {
+ if (type >= 0 && type < mNetworkByteActivityCounters.length) {
+ return mNetworkByteActivityCounters[type].getCountLocked(which);
} else {
return 0;
}
}
+ @Override
+ public long getNetworkActivityPackets(int type, int which) {
+ if (type >= 0 && type < mNetworkPacketActivityCounters.length) {
+ return mNetworkPacketActivityCounters[type].getCountLocked(which);
+ } else {
+ return 0;
+ }
+ }
+
+ @Override public long getStartClockTime() {
+ return mStartClockTime;
+ }
+
@Override public boolean getIsOnBattery() {
return mOnBattery;
}
@@ -2627,7 +3604,10 @@ public final class BatteryStatsImpl extends BatteryStats {
Counter[] mUserActivityCounters;
- LongSamplingCounter[] mNetworkActivityCounters;
+ LongSamplingCounter[] mNetworkByteActivityCounters;
+ LongSamplingCounter[] mNetworkPacketActivityCounters;
+ LongSamplingCounter mMobileRadioActiveTime;
+ LongSamplingCounter mMobileRadioActiveCount;
/**
* The statistics we have collected for this uid's wake locks.
@@ -2657,14 +3637,14 @@ public final class BatteryStatsImpl extends BatteryStats {
public Uid(int uid) {
mUid = uid;
mWifiRunningTimer = new StopwatchTimer(Uid.this, WIFI_RUNNING,
- mWifiRunningTimers, mUnpluggables);
+ mWifiRunningTimers, mOnBatteryTimeBase);
mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
- mFullWifiLockTimers, mUnpluggables);
+ mFullWifiLockTimers, mOnBatteryTimeBase);
mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN,
- mWifiScanTimers, mUnpluggables);
+ mWifiScanTimers, mOnBatteryTimeBase);
mWifiBatchedScanTimer = new StopwatchTimer[NUM_WIFI_BATCHED_SCAN_BINS];
mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
- mWifiMulticastTimers, mUnpluggables);
+ mWifiMulticastTimers, mOnBatteryTimeBase);
}
@Override
@@ -2693,67 +3673,67 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
- public void noteWifiRunningLocked() {
+ public void noteWifiRunningLocked(long elapsedRealtimeMs) {
if (!mWifiRunning) {
mWifiRunning = true;
if (mWifiRunningTimer == null) {
mWifiRunningTimer = new StopwatchTimer(Uid.this, WIFI_RUNNING,
- mWifiRunningTimers, mUnpluggables);
+ mWifiRunningTimers, mOnBatteryTimeBase);
}
- mWifiRunningTimer.startRunningLocked(BatteryStatsImpl.this);
+ mWifiRunningTimer.startRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteWifiStoppedLocked() {
+ public void noteWifiStoppedLocked(long elapsedRealtimeMs) {
if (mWifiRunning) {
mWifiRunning = false;
- mWifiRunningTimer.stopRunningLocked(BatteryStatsImpl.this);
+ mWifiRunningTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteFullWifiLockAcquiredLocked() {
+ public void noteFullWifiLockAcquiredLocked(long elapsedRealtimeMs) {
if (!mFullWifiLockOut) {
mFullWifiLockOut = true;
if (mFullWifiLockTimer == null) {
mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
- mFullWifiLockTimers, mUnpluggables);
+ mFullWifiLockTimers, mOnBatteryTimeBase);
}
- mFullWifiLockTimer.startRunningLocked(BatteryStatsImpl.this);
+ mFullWifiLockTimer.startRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteFullWifiLockReleasedLocked() {
+ public void noteFullWifiLockReleasedLocked(long elapsedRealtimeMs) {
if (mFullWifiLockOut) {
mFullWifiLockOut = false;
- mFullWifiLockTimer.stopRunningLocked(BatteryStatsImpl.this);
+ mFullWifiLockTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteWifiScanStartedLocked() {
+ public void noteWifiScanStartedLocked(long elapsedRealtimeMs) {
if (!mWifiScanStarted) {
mWifiScanStarted = true;
if (mWifiScanTimer == null) {
mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN,
- mWifiScanTimers, mUnpluggables);
+ mWifiScanTimers, mOnBatteryTimeBase);
}
- mWifiScanTimer.startRunningLocked(BatteryStatsImpl.this);
+ mWifiScanTimer.startRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteWifiScanStoppedLocked() {
+ public void noteWifiScanStoppedLocked(long elapsedRealtimeMs) {
if (mWifiScanStarted) {
mWifiScanStarted = false;
- mWifiScanTimer.stopRunningLocked(BatteryStatsImpl.this);
+ mWifiScanTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteWifiBatchedScanStartedLocked(int csph) {
+ public void noteWifiBatchedScanStartedLocked(int csph, long elapsedRealtimeMs) {
int bin = 0;
while (csph > 8 && bin < NUM_WIFI_BATCHED_SCAN_BINS) {
csph = csph >> 3;
@@ -2764,66 +3744,66 @@ public final class BatteryStatsImpl extends BatteryStats {
if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) {
mWifiBatchedScanTimer[mWifiBatchedScanBinStarted].
- stopRunningLocked(BatteryStatsImpl.this);
+ stopRunningLocked(elapsedRealtimeMs);
}
mWifiBatchedScanBinStarted = bin;
if (mWifiBatchedScanTimer[bin] == null) {
makeWifiBatchedScanBin(bin, null);
}
- mWifiBatchedScanTimer[bin].startRunningLocked(BatteryStatsImpl.this);
+ mWifiBatchedScanTimer[bin].startRunningLocked(elapsedRealtimeMs);
}
@Override
- public void noteWifiBatchedScanStoppedLocked() {
+ public void noteWifiBatchedScanStoppedLocked(long elapsedRealtimeMs) {
if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) {
mWifiBatchedScanTimer[mWifiBatchedScanBinStarted].
- stopRunningLocked(BatteryStatsImpl.this);
+ stopRunningLocked(elapsedRealtimeMs);
mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED;
}
}
@Override
- public void noteWifiMulticastEnabledLocked() {
+ public void noteWifiMulticastEnabledLocked(long elapsedRealtimeMs) {
if (!mWifiMulticastEnabled) {
mWifiMulticastEnabled = true;
if (mWifiMulticastTimer == null) {
mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
- mWifiMulticastTimers, mUnpluggables);
+ mWifiMulticastTimers, mOnBatteryTimeBase);
}
- mWifiMulticastTimer.startRunningLocked(BatteryStatsImpl.this);
+ mWifiMulticastTimer.startRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteWifiMulticastDisabledLocked() {
+ public void noteWifiMulticastDisabledLocked(long elapsedRealtimeMs) {
if (mWifiMulticastEnabled) {
mWifiMulticastEnabled = false;
- mWifiMulticastTimer.stopRunningLocked(BatteryStatsImpl.this);
+ mWifiMulticastTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
public StopwatchTimer createAudioTurnedOnTimerLocked() {
if (mAudioTurnedOnTimer == null) {
mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
- null, mUnpluggables);
+ null, mOnBatteryTimeBase);
}
return mAudioTurnedOnTimer;
}
@Override
- public void noteAudioTurnedOnLocked() {
+ public void noteAudioTurnedOnLocked(long elapsedRealtimeMs) {
if (!mAudioTurnedOn) {
mAudioTurnedOn = true;
- createAudioTurnedOnTimerLocked().startRunningLocked(BatteryStatsImpl.this);
+ createAudioTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteAudioTurnedOffLocked() {
+ public void noteAudioTurnedOffLocked(long elapsedRealtimeMs) {
if (mAudioTurnedOn) {
mAudioTurnedOn = false;
if (mAudioTurnedOnTimer != null) {
- mAudioTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+ mAudioTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
}
@@ -2831,25 +3811,25 @@ public final class BatteryStatsImpl extends BatteryStats {
public StopwatchTimer createVideoTurnedOnTimerLocked() {
if (mVideoTurnedOnTimer == null) {
mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
- null, mUnpluggables);
+ null, mOnBatteryTimeBase);
}
return mVideoTurnedOnTimer;
}
@Override
- public void noteVideoTurnedOnLocked() {
+ public void noteVideoTurnedOnLocked(long elapsedRealtimeMs) {
if (!mVideoTurnedOn) {
mVideoTurnedOn = true;
- createVideoTurnedOnTimerLocked().startRunningLocked(BatteryStatsImpl.this);
+ createVideoTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteVideoTurnedOffLocked() {
+ public void noteVideoTurnedOffLocked(long elapsedRealtimeMs) {
if (mVideoTurnedOn) {
mVideoTurnedOn = false;
if (mVideoTurnedOnTimer != null) {
- mVideoTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+ mVideoTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
}
@@ -2857,28 +3837,27 @@ public final class BatteryStatsImpl extends BatteryStats {
public StopwatchTimer createForegroundActivityTimerLocked() {
if (mForegroundActivityTimer == null) {
mForegroundActivityTimer = new StopwatchTimer(
- Uid.this, FOREGROUND_ACTIVITY, null, mUnpluggables);
+ Uid.this, FOREGROUND_ACTIVITY, null, mOnBatteryTimeBase);
}
return mForegroundActivityTimer;
}
@Override
- public void noteActivityResumedLocked() {
+ public void noteActivityResumedLocked(long elapsedRealtimeMs) {
// We always start, since we want multiple foreground PIDs to nest
- createForegroundActivityTimerLocked().startRunningLocked(BatteryStatsImpl.this);
+ createForegroundActivityTimerLocked().startRunningLocked(elapsedRealtimeMs);
}
@Override
- public void noteActivityPausedLocked() {
+ public void noteActivityPausedLocked(long elapsedRealtimeMs) {
if (mForegroundActivityTimer != null) {
- mForegroundActivityTimer.stopRunningLocked(BatteryStatsImpl.this);
+ mForegroundActivityTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
public BatchTimer createVibratorOnTimerLocked() {
if (mVibratorOnTimer == null) {
- mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON,
- mUnpluggables, BatteryStatsImpl.this.mOnBatteryInternal);
+ mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON, mOnBatteryTimeBase);
}
return mVibratorOnTimer;
}
@@ -2894,61 +3873,60 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
- public long getWifiRunningTime(long batteryRealtime, int which) {
+ public long getWifiRunningTime(long elapsedRealtimeUs, int which) {
if (mWifiRunningTimer == null) {
return 0;
}
- return mWifiRunningTimer.getTotalTimeLocked(batteryRealtime, which);
+ return mWifiRunningTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
- public long getFullWifiLockTime(long batteryRealtime, int which) {
+ public long getFullWifiLockTime(long elapsedRealtimeUs, int which) {
if (mFullWifiLockTimer == null) {
return 0;
}
- return mFullWifiLockTimer.getTotalTimeLocked(batteryRealtime, which);
+ return mFullWifiLockTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
- public long getWifiScanTime(long batteryRealtime, int which) {
+ public long getWifiScanTime(long elapsedRealtimeUs, int which) {
if (mWifiScanTimer == null) {
return 0;
}
- return mWifiScanTimer.getTotalTimeLocked(batteryRealtime, which);
+ return mWifiScanTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
- public long getWifiBatchedScanTime(int csphBin, long batteryRealtime, int which) {
+ public long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which) {
if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0;
if (mWifiBatchedScanTimer[csphBin] == null) {
return 0;
}
- return mWifiBatchedScanTimer[csphBin].getTotalTimeLocked(batteryRealtime, which);
+ return mWifiBatchedScanTimer[csphBin].getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
- public long getWifiMulticastTime(long batteryRealtime, int which) {
+ public long getWifiMulticastTime(long elapsedRealtimeUs, int which) {
if (mWifiMulticastTimer == null) {
return 0;
}
- return mWifiMulticastTimer.getTotalTimeLocked(batteryRealtime,
- which);
+ return mWifiMulticastTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
- public long getAudioTurnedOnTime(long batteryRealtime, int which) {
+ public long getAudioTurnedOnTime(long elapsedRealtimeUs, int which) {
if (mAudioTurnedOnTimer == null) {
return 0;
}
- return mAudioTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ return mAudioTurnedOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
- public long getVideoTurnedOnTime(long batteryRealtime, int which) {
+ public long getVideoTurnedOnTime(long elapsedRealtimeUs, int which) {
if (mVideoTurnedOnTimer == null) {
return 0;
}
- return mVideoTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ return mVideoTurnedOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
@@ -2997,10 +3975,10 @@ public final class BatteryStatsImpl extends BatteryStats {
}
if (in == null) {
mWifiBatchedScanTimer[i] = new StopwatchTimer(this, WIFI_BATCHED_SCAN, collected,
- mUnpluggables);
+ mOnBatteryTimeBase);
} else {
mWifiBatchedScanTimer[i] = new StopwatchTimer(this, WIFI_BATCHED_SCAN, collected,
- mUnpluggables, in);
+ mOnBatteryTimeBase, in);
}
}
@@ -3008,42 +3986,77 @@ public final class BatteryStatsImpl extends BatteryStats {
void initUserActivityLocked() {
mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES];
for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
- mUserActivityCounters[i] = new Counter(mUnpluggables);
+ mUserActivityCounters[i] = new Counter(mOnBatteryTimeBase);
}
}
- void noteNetworkActivityLocked(int type, long delta) {
- if (mNetworkActivityCounters == null) {
+ void noteNetworkActivityLocked(int type, long deltaBytes, long deltaPackets) {
+ if (mNetworkByteActivityCounters == null) {
initNetworkActivityLocked();
}
if (type >= 0 && type < NUM_NETWORK_ACTIVITY_TYPES) {
- mNetworkActivityCounters[type].addCountLocked(delta);
+ mNetworkByteActivityCounters[type].addCountLocked(deltaBytes);
+ mNetworkPacketActivityCounters[type].addCountLocked(deltaPackets);
} else {
Slog.w(TAG, "Unknown network activity type " + type + " was specified.",
new Throwable());
}
}
+ void noteMobileRadioActiveTimeLocked(long batteryUptime) {
+ if (mNetworkByteActivityCounters == null) {
+ initNetworkActivityLocked();
+ }
+ mMobileRadioActiveTime.addCountLocked(batteryUptime);
+ mMobileRadioActiveCount.addCountLocked(1);
+ }
+
@Override
public boolean hasNetworkActivity() {
- return mNetworkActivityCounters != null;
+ return mNetworkByteActivityCounters != null;
+ }
+
+ @Override
+ public long getNetworkActivityBytes(int type, int which) {
+ if (mNetworkByteActivityCounters != null && type >= 0
+ && type < mNetworkByteActivityCounters.length) {
+ return mNetworkByteActivityCounters[type].getCountLocked(which);
+ } else {
+ return 0;
+ }
}
@Override
- public long getNetworkActivityCount(int type, int which) {
- if (mNetworkActivityCounters != null && type >= 0
- && type < mNetworkActivityCounters.length) {
- return mNetworkActivityCounters[type].getCountLocked(which);
+ public long getNetworkActivityPackets(int type, int which) {
+ if (mNetworkPacketActivityCounters != null && type >= 0
+ && type < mNetworkPacketActivityCounters.length) {
+ return mNetworkPacketActivityCounters[type].getCountLocked(which);
} else {
return 0;
}
}
+ @Override
+ public long getMobileRadioActiveTime(int which) {
+ return mMobileRadioActiveTime != null
+ ? mMobileRadioActiveTime.getCountLocked(which) : 0;
+ }
+
+ @Override
+ public int getMobileRadioActiveCount(int which) {
+ return mMobileRadioActiveCount != null
+ ? (int)mMobileRadioActiveCount.getCountLocked(which) : 0;
+ }
+
void initNetworkActivityLocked() {
- mNetworkActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+ mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+ mNetworkPacketActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables);
+ mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+ mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
}
+ mMobileRadioActiveTime = new LongSamplingCounter(mOnBatteryTimeBase);
+ mMobileRadioActiveCount = new LongSamplingCounter(mOnBatteryTimeBase);
}
/**
@@ -3054,42 +4067,42 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean active = false;
if (mWifiRunningTimer != null) {
- active |= !mWifiRunningTimer.reset(BatteryStatsImpl.this, false);
+ active |= !mWifiRunningTimer.reset(false);
active |= mWifiRunning;
}
if (mFullWifiLockTimer != null) {
- active |= !mFullWifiLockTimer.reset(BatteryStatsImpl.this, false);
+ active |= !mFullWifiLockTimer.reset(false);
active |= mFullWifiLockOut;
}
if (mWifiScanTimer != null) {
- active |= !mWifiScanTimer.reset(BatteryStatsImpl.this, false);
+ active |= !mWifiScanTimer.reset(false);
active |= mWifiScanStarted;
}
if (mWifiBatchedScanTimer != null) {
for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) {
if (mWifiBatchedScanTimer[i] != null) {
- active |= !mWifiBatchedScanTimer[i].reset(BatteryStatsImpl.this, false);
+ active |= !mWifiBatchedScanTimer[i].reset(false);
}
}
active |= (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED);
}
if (mWifiMulticastTimer != null) {
- active |= !mWifiMulticastTimer.reset(BatteryStatsImpl.this, false);
+ active |= !mWifiMulticastTimer.reset(false);
active |= mWifiMulticastEnabled;
}
if (mAudioTurnedOnTimer != null) {
- active |= !mAudioTurnedOnTimer.reset(BatteryStatsImpl.this, false);
+ active |= !mAudioTurnedOnTimer.reset(false);
active |= mAudioTurnedOn;
}
if (mVideoTurnedOnTimer != null) {
- active |= !mVideoTurnedOnTimer.reset(BatteryStatsImpl.this, false);
+ active |= !mVideoTurnedOnTimer.reset(false);
active |= mVideoTurnedOn;
}
if (mForegroundActivityTimer != null) {
- active |= !mForegroundActivityTimer.reset(BatteryStatsImpl.this, false);
+ active |= !mForegroundActivityTimer.reset(false);
}
if (mVibratorOnTimer != null) {
- if (mVibratorOnTimer.reset(BatteryStatsImpl.this, false)) {
+ if (mVibratorOnTimer.reset(false)) {
mVibratorOnTimer.detach();
mVibratorOnTimer = null;
} else {
@@ -3103,10 +4116,13 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- if (mNetworkActivityCounters != null) {
+ if (mNetworkByteActivityCounters != null) {
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i].reset(false);
+ mNetworkByteActivityCounters[i].reset(false);
+ mNetworkPacketActivityCounters[i].reset(false);
}
+ mMobileRadioActiveTime.reset(false);
+ mMobileRadioActiveCount.reset(false);
}
if (mWakelockStats.size() > 0) {
@@ -3142,10 +4158,12 @@ public final class BatteryStatsImpl extends BatteryStats {
mProcessStats.clear();
}
if (mPids.size() > 0) {
- for (int i=0; !active && i<mPids.size(); i++) {
+ for (int i=mPids.size()-1; i>=0; i--) {
Pid pid = mPids.valueAt(i);
- if (pid.mWakeStart != 0) {
+ if (pid.mWakeNesting > 0) {
active = true;
+ } else {
+ mPids.removeAt(i);
}
}
}
@@ -3167,8 +4185,6 @@ public final class BatteryStatsImpl extends BatteryStats {
mPackageStats.clear();
}
- mPids.clear();
-
if (!active) {
if (mWifiRunningTimer != null) {
mWifiRunningTimer.detach();
@@ -3204,29 +4220,31 @@ public final class BatteryStatsImpl extends BatteryStats {
mUserActivityCounters[i].detach();
}
}
- if (mNetworkActivityCounters != null) {
+ if (mNetworkByteActivityCounters != null) {
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i].detach();
+ mNetworkByteActivityCounters[i].detach();
+ mNetworkPacketActivityCounters[i].detach();
}
}
+ mPids.clear();
}
return !active;
}
- void writeToParcelLocked(Parcel out, long batteryRealtime) {
+ void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
out.writeInt(mWakelockStats.size());
for (Map.Entry<String, Uid.Wakelock> wakelockEntry : mWakelockStats.entrySet()) {
out.writeString(wakelockEntry.getKey());
Uid.Wakelock wakelock = wakelockEntry.getValue();
- wakelock.writeToParcelLocked(out, batteryRealtime);
+ wakelock.writeToParcelLocked(out, elapsedRealtimeUs);
}
out.writeInt(mSensorStats.size());
for (Map.Entry<Integer, Uid.Sensor> sensorEntry : mSensorStats.entrySet()) {
out.writeInt(sensorEntry.getKey());
Uid.Sensor sensor = sensorEntry.getValue();
- sensor.writeToParcelLocked(out, batteryRealtime);
+ sensor.writeToParcelLocked(out, elapsedRealtimeUs);
}
out.writeInt(mProcessStats.size());
@@ -3245,57 +4263,57 @@ public final class BatteryStatsImpl extends BatteryStats {
if (mWifiRunningTimer != null) {
out.writeInt(1);
- mWifiRunningTimer.writeToParcel(out, batteryRealtime);
+ mWifiRunningTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
if (mFullWifiLockTimer != null) {
out.writeInt(1);
- mFullWifiLockTimer.writeToParcel(out, batteryRealtime);
+ mFullWifiLockTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
if (mWifiScanTimer != null) {
out.writeInt(1);
- mWifiScanTimer.writeToParcel(out, batteryRealtime);
+ mWifiScanTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) {
if (mWifiBatchedScanTimer[i] != null) {
out.writeInt(1);
- mWifiBatchedScanTimer[i].writeToParcel(out, batteryRealtime);
+ mWifiBatchedScanTimer[i].writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
}
if (mWifiMulticastTimer != null) {
out.writeInt(1);
- mWifiMulticastTimer.writeToParcel(out, batteryRealtime);
+ mWifiMulticastTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
if (mAudioTurnedOnTimer != null) {
out.writeInt(1);
- mAudioTurnedOnTimer.writeToParcel(out, batteryRealtime);
+ mAudioTurnedOnTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
if (mVideoTurnedOnTimer != null) {
out.writeInt(1);
- mVideoTurnedOnTimer.writeToParcel(out, batteryRealtime);
+ mVideoTurnedOnTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
if (mForegroundActivityTimer != null) {
out.writeInt(1);
- mForegroundActivityTimer.writeToParcel(out, batteryRealtime);
+ mForegroundActivityTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
if (mVibratorOnTimer != null) {
out.writeInt(1);
- mVibratorOnTimer.writeToParcel(out, batteryRealtime);
+ mVibratorOnTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
@@ -3307,23 +4325,26 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
- if (mNetworkActivityCounters != null) {
+ if (mNetworkByteActivityCounters != null) {
out.writeInt(1);
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i].writeToParcel(out);
+ mNetworkByteActivityCounters[i].writeToParcel(out);
+ mNetworkPacketActivityCounters[i].writeToParcel(out);
}
+ mMobileRadioActiveTime.writeToParcel(out);
+ mMobileRadioActiveCount.writeToParcel(out);
} else {
out.writeInt(0);
}
}
- void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
+ void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
int numWakelocks = in.readInt();
mWakelockStats.clear();
for (int j = 0; j < numWakelocks; j++) {
String wakelockName = in.readString();
Uid.Wakelock wakelock = new Wakelock();
- wakelock.readFromParcelLocked(unpluggables, in);
+ wakelock.readFromParcelLocked(timeBase, screenOffTimeBase, in);
// We will just drop some random set of wakelocks if
// the previous run of the system was an older version
// that didn't impose a limit.
@@ -3335,7 +4356,7 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int k = 0; k < numSensors; k++) {
int sensorNumber = in.readInt();
Uid.Sensor sensor = new Sensor(sensorNumber);
- sensor.readFromParcelLocked(mUnpluggables, in);
+ sensor.readFromParcelLocked(mOnBatteryTimeBase, in);
mSensorStats.put(sensorNumber, sensor);
}
@@ -3360,21 +4381,21 @@ public final class BatteryStatsImpl extends BatteryStats {
mWifiRunning = false;
if (in.readInt() != 0) {
mWifiRunningTimer = new StopwatchTimer(Uid.this, WIFI_RUNNING,
- mWifiRunningTimers, mUnpluggables, in);
+ mWifiRunningTimers, mOnBatteryTimeBase, in);
} else {
mWifiRunningTimer = null;
}
mFullWifiLockOut = false;
if (in.readInt() != 0) {
mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
- mFullWifiLockTimers, mUnpluggables, in);
+ mFullWifiLockTimers, mOnBatteryTimeBase, in);
} else {
mFullWifiLockTimer = null;
}
mWifiScanStarted = false;
if (in.readInt() != 0) {
mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN,
- mWifiScanTimers, mUnpluggables, in);
+ mWifiScanTimers, mOnBatteryTimeBase, in);
} else {
mWifiScanTimer = null;
}
@@ -3389,51 +4410,58 @@ public final class BatteryStatsImpl extends BatteryStats {
mWifiMulticastEnabled = false;
if (in.readInt() != 0) {
mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
- mWifiMulticastTimers, mUnpluggables, in);
+ mWifiMulticastTimers, mOnBatteryTimeBase, in);
} else {
mWifiMulticastTimer = null;
}
mAudioTurnedOn = false;
if (in.readInt() != 0) {
mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
- null, mUnpluggables, in);
+ null, mOnBatteryTimeBase, in);
} else {
mAudioTurnedOnTimer = null;
}
mVideoTurnedOn = false;
if (in.readInt() != 0) {
mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
- null, mUnpluggables, in);
+ null, mOnBatteryTimeBase, in);
} else {
mVideoTurnedOnTimer = null;
}
if (in.readInt() != 0) {
mForegroundActivityTimer = new StopwatchTimer(
- Uid.this, FOREGROUND_ACTIVITY, null, mUnpluggables, in);
+ Uid.this, FOREGROUND_ACTIVITY, null, mOnBatteryTimeBase, in);
} else {
mForegroundActivityTimer = null;
}
if (in.readInt() != 0) {
- mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON,
- mUnpluggables, BatteryStatsImpl.this.mOnBatteryInternal, in);
+ mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON, mOnBatteryTimeBase, in);
} else {
mVibratorOnTimer = null;
}
if (in.readInt() != 0) {
mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES];
for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
- mUserActivityCounters[i] = new Counter(mUnpluggables, in);
+ mUserActivityCounters[i] = new Counter(mOnBatteryTimeBase, in);
}
} else {
mUserActivityCounters = null;
}
if (in.readInt() != 0) {
- mNetworkActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+ mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+ mNetworkPacketActivityCounters
+ = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables, in);
+ mNetworkByteActivityCounters[i]
+ = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mNetworkPacketActivityCounters[i]
+ = new LongSamplingCounter(mOnBatteryTimeBase, in);
}
+ mMobileRadioActiveTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mMobileRadioActiveCount = new LongSamplingCounter(mOnBatteryTimeBase, in);
} else {
- mNetworkActivityCounters = null;
+ mNetworkByteActivityCounters = null;
+ mNetworkPacketActivityCounters = null;
}
}
@@ -3464,24 +4492,24 @@ public final class BatteryStatsImpl extends BatteryStats {
* return a new Timer, or null.
*/
private StopwatchTimer readTimerFromParcel(int type, ArrayList<StopwatchTimer> pool,
- ArrayList<Unpluggable> unpluggables, Parcel in) {
+ TimeBase timeBase, Parcel in) {
if (in.readInt() == 0) {
return null;
}
- return new StopwatchTimer(Uid.this, type, pool, unpluggables, in);
+ return new StopwatchTimer(Uid.this, type, pool, timeBase, in);
}
boolean reset() {
boolean wlactive = false;
if (mTimerFull != null) {
- wlactive |= !mTimerFull.reset(BatteryStatsImpl.this, false);
+ wlactive |= !mTimerFull.reset(false);
}
if (mTimerPartial != null) {
- wlactive |= !mTimerPartial.reset(BatteryStatsImpl.this, false);
+ wlactive |= !mTimerPartial.reset(false);
}
if (mTimerWindow != null) {
- wlactive |= !mTimerWindow.reset(BatteryStatsImpl.this, false);
+ wlactive |= !mTimerWindow.reset(false);
}
if (!wlactive) {
if (mTimerFull != null) {
@@ -3500,19 +4528,19 @@ public final class BatteryStatsImpl extends BatteryStats {
return !wlactive;
}
- void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
+ void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
mTimerPartial = readTimerFromParcel(WAKE_TYPE_PARTIAL,
- mPartialTimers, unpluggables, in);
+ mPartialTimers, screenOffTimeBase, in);
mTimerFull = readTimerFromParcel(WAKE_TYPE_FULL,
- mFullTimers, unpluggables, in);
+ mFullTimers, timeBase, in);
mTimerWindow = readTimerFromParcel(WAKE_TYPE_WINDOW,
- mWindowTimers, unpluggables, in);
+ mWindowTimers, timeBase, in);
}
- void writeToParcelLocked(Parcel out, long batteryRealtime) {
- Timer.writeTimerToParcel(out, mTimerPartial, batteryRealtime);
- Timer.writeTimerToParcel(out, mTimerFull, batteryRealtime);
- Timer.writeTimerToParcel(out, mTimerWindow, batteryRealtime);
+ void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
+ Timer.writeTimerToParcel(out, mTimerPartial, elapsedRealtimeUs);
+ Timer.writeTimerToParcel(out, mTimerFull, elapsedRealtimeUs);
+ Timer.writeTimerToParcel(out, mTimerWindow, elapsedRealtimeUs);
}
@Override
@@ -3534,8 +4562,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mHandle = handle;
}
- private StopwatchTimer readTimerFromParcel(ArrayList<Unpluggable> unpluggables,
- Parcel in) {
+ private StopwatchTimer readTimerFromParcel(TimeBase timeBase, Parcel in) {
if (in.readInt() == 0) {
return null;
}
@@ -3545,23 +4572,23 @@ public final class BatteryStatsImpl extends BatteryStats {
pool = new ArrayList<StopwatchTimer>();
mSensorTimers.put(mHandle, pool);
}
- return new StopwatchTimer(Uid.this, 0, pool, unpluggables, in);
+ return new StopwatchTimer(Uid.this, 0, pool, timeBase, in);
}
boolean reset() {
- if (mTimer.reset(BatteryStatsImpl.this, true)) {
+ if (mTimer.reset(true)) {
mTimer = null;
return true;
}
return false;
}
- void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
- mTimer = readTimerFromParcel(unpluggables, in);
+ void readFromParcelLocked(TimeBase timeBase, Parcel in) {
+ mTimer = readTimerFromParcel(timeBase, in);
}
- void writeToParcelLocked(Parcel out, long batteryRealtime) {
- Timer.writeTimerToParcel(out, mTimer, batteryRealtime);
+ void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
+ Timer.writeTimerToParcel(out, mTimer, elapsedRealtimeUs);
}
@Override
@@ -3578,7 +4605,12 @@ public final class BatteryStatsImpl extends BatteryStats {
/**
* The statistics associated with a particular process.
*/
- public final class Proc extends BatteryStats.Uid.Proc implements Unpluggable {
+ public final class Proc extends BatteryStats.Uid.Proc implements TimeBaseObs {
+ /**
+ * Remains true until removed from the stats.
+ */
+ boolean mActive = true;
+
/**
* Total time (in 1/100 sec) spent executing in user code.
*/
@@ -3664,26 +4696,27 @@ public final class BatteryStatsImpl extends BatteryStats {
ArrayList<ExcessivePower> mExcessivePower;
Proc() {
- mUnpluggables.add(this);
+ mOnBatteryTimeBase.add(this);
mSpeedBins = new SamplingCounter[getCpuSpeedSteps()];
}
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
mUnpluggedUserTime = mUserTime;
mUnpluggedSystemTime = mSystemTime;
mUnpluggedForegroundTime = mForegroundTime;
mUnpluggedStarts = mStarts;
}
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
}
void detach() {
- mUnpluggables.remove(this);
+ mActive = false;
+ mOnBatteryTimeBase.remove(this);
for (int i = 0; i < mSpeedBins.length; i++) {
SamplingCounter c = mSpeedBins[i];
if (c != null) {
- mUnpluggables.remove(c);
+ mOnBatteryTimeBase.remove(c);
mSpeedBins[i] = null;
}
}
@@ -3812,7 +4845,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mSpeedBins = new SamplingCounter[bins >= steps ? bins : steps];
for (int i = 0; i < bins; i++) {
if (in.readInt() != 0) {
- mSpeedBins[i] = new SamplingCounter(mUnpluggables, in);
+ mSpeedBins[i] = new SamplingCounter(mOnBatteryTimeBase, in);
}
}
@@ -3837,65 +4870,50 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
+ public boolean isActive() {
+ return mActive;
+ }
+
+ @Override
public long getUserTime(int which) {
- long val;
- if (which == STATS_LAST) {
- val = mLastUserTime;
- } else {
- val = mUserTime;
- if (which == STATS_CURRENT) {
- val -= mLoadedUserTime;
- } else if (which == STATS_SINCE_UNPLUGGED) {
- val -= mUnpluggedUserTime;
- }
+ long val = mUserTime;
+ if (which == STATS_CURRENT) {
+ val -= mLoadedUserTime;
+ } else if (which == STATS_SINCE_UNPLUGGED) {
+ val -= mUnpluggedUserTime;
}
return val;
}
@Override
public long getSystemTime(int which) {
- long val;
- if (which == STATS_LAST) {
- val = mLastSystemTime;
- } else {
- val = mSystemTime;
- if (which == STATS_CURRENT) {
- val -= mLoadedSystemTime;
- } else if (which == STATS_SINCE_UNPLUGGED) {
- val -= mUnpluggedSystemTime;
- }
+ long val = mSystemTime;
+ if (which == STATS_CURRENT) {
+ val -= mLoadedSystemTime;
+ } else if (which == STATS_SINCE_UNPLUGGED) {
+ val -= mUnpluggedSystemTime;
}
return val;
}
@Override
public long getForegroundTime(int which) {
- long val;
- if (which == STATS_LAST) {
- val = mLastForegroundTime;
- } else {
- val = mForegroundTime;
- if (which == STATS_CURRENT) {
- val -= mLoadedForegroundTime;
- } else if (which == STATS_SINCE_UNPLUGGED) {
- val -= mUnpluggedForegroundTime;
- }
+ long val = mForegroundTime;
+ if (which == STATS_CURRENT) {
+ val -= mLoadedForegroundTime;
+ } else if (which == STATS_SINCE_UNPLUGGED) {
+ val -= mUnpluggedForegroundTime;
}
return val;
}
@Override
public int getStarts(int which) {
- int val;
- if (which == STATS_LAST) {
- val = mLastStarts;
- } else {
- val = mStarts;
- if (which == STATS_CURRENT) {
- val -= mLoadedStarts;
- } else if (which == STATS_SINCE_UNPLUGGED) {
- val -= mUnpluggedStarts;
- }
+ int val = mStarts;
+ if (which == STATS_CURRENT) {
+ val -= mLoadedStarts;
+ } else if (which == STATS_SINCE_UNPLUGGED) {
+ val -= mUnpluggedStarts;
}
return val;
}
@@ -3907,7 +4925,7 @@ public final class BatteryStatsImpl extends BatteryStats {
if (amt != 0) {
SamplingCounter c = mSpeedBins[i];
if (c == null) {
- mSpeedBins[i] = c = new SamplingCounter(mUnpluggables);
+ mSpeedBins[i] = c = new SamplingCounter(mOnBatteryTimeBase);
}
c.addCountAtomic(values[i]);
}
@@ -3928,7 +4946,7 @@ public final class BatteryStatsImpl extends BatteryStats {
/**
* The statistics associated with a particular package.
*/
- public final class Pkg extends BatteryStats.Uid.Pkg implements Unpluggable {
+ public final class Pkg extends BatteryStats.Uid.Pkg implements TimeBaseObs {
/**
* Number of times this package has done something that could wake up the
* device from sleep.
@@ -3959,18 +4977,18 @@ public final class BatteryStatsImpl extends BatteryStats {
final HashMap<String, Serv> mServiceStats = new HashMap<String, Serv>();
Pkg() {
- mUnpluggables.add(this);
+ mOnBatteryScreenOffTimeBase.add(this);
}
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
mUnpluggedWakeups = mWakeups;
}
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
}
void detach() {
- mUnpluggables.remove(this);
+ mOnBatteryScreenOffTimeBase.remove(this);
}
void readFromParcelLocked(Parcel in) {
@@ -4011,16 +5029,11 @@ public final class BatteryStatsImpl extends BatteryStats {
@Override
public int getWakeups(int which) {
- int val;
- if (which == STATS_LAST) {
- val = mLastWakeups;
- } else {
- val = mWakeups;
- if (which == STATS_CURRENT) {
- val -= mLoadedWakeups;
- } else if (which == STATS_SINCE_UNPLUGGED) {
- val -= mUnpluggedWakeups;
- }
+ int val = mWakeups;
+ if (which == STATS_CURRENT) {
+ val -= mLoadedWakeups;
+ } else if (which == STATS_SINCE_UNPLUGGED) {
+ val -= mUnpluggedWakeups;
}
return val;
@@ -4029,7 +5042,7 @@ public final class BatteryStatsImpl extends BatteryStats {
/**
* The statistics associated with a particular service.
*/
- public final class Serv extends BatteryStats.Uid.Pkg.Serv implements Unpluggable {
+ public final class Serv extends BatteryStats.Uid.Pkg.Serv implements TimeBaseObs {
/**
* Total time (ms in battery uptime) the service has been left started.
*/
@@ -4121,20 +5134,22 @@ public final class BatteryStatsImpl extends BatteryStats {
int mUnpluggedLaunches;
Serv() {
- mUnpluggables.add(this);
+ mOnBatteryTimeBase.add(this);
}
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
- mUnpluggedStartTime = getStartTimeToNowLocked(batteryUptime);
+ public void onTimeStarted(long elapsedRealtime, long baseUptime,
+ long baseRealtime) {
+ mUnpluggedStartTime = getStartTimeToNowLocked(baseUptime);
mUnpluggedStarts = mStarts;
mUnpluggedLaunches = mLaunches;
}
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime,
+ long baseRealtime) {
}
void detach() {
- mUnpluggables.remove(this);
+ mOnBatteryTimeBase.remove(this);
}
void readFromParcelLocked(Parcel in) {
@@ -4230,51 +5245,33 @@ public final class BatteryStatsImpl extends BatteryStats {
@Override
public int getLaunches(int which) {
- int val;
-
- if (which == STATS_LAST) {
- val = mLastLaunches;
- } else {
- val = mLaunches;
- if (which == STATS_CURRENT) {
- val -= mLoadedLaunches;
- } else if (which == STATS_SINCE_UNPLUGGED) {
- val -= mUnpluggedLaunches;
- }
+ int val = mLaunches;
+ if (which == STATS_CURRENT) {
+ val -= mLoadedLaunches;
+ } else if (which == STATS_SINCE_UNPLUGGED) {
+ val -= mUnpluggedLaunches;
}
-
return val;
}
@Override
public long getStartTime(long now, int which) {
- long val;
- if (which == STATS_LAST) {
- val = mLastStartTime;
- } else {
- val = getStartTimeToNowLocked(now);
- if (which == STATS_CURRENT) {
- val -= mLoadedStartTime;
- } else if (which == STATS_SINCE_UNPLUGGED) {
- val -= mUnpluggedStartTime;
- }
+ long val = getStartTimeToNowLocked(now);
+ if (which == STATS_CURRENT) {
+ val -= mLoadedStartTime;
+ } else if (which == STATS_SINCE_UNPLUGGED) {
+ val -= mUnpluggedStartTime;
}
-
return val;
}
@Override
public int getStarts(int which) {
- int val;
- if (which == STATS_LAST) {
- val = mLastStarts;
- } else {
- val = mStarts;
- if (which == STATS_CURRENT) {
- val -= mLoadedStarts;
- } else if (which == STATS_SINCE_UNPLUGGED) {
- val -= mUnpluggedStarts;
- }
+ int val = mStarts;
+ if (which == STATS_CURRENT) {
+ val -= mLoadedStarts;
+ } else if (which == STATS_SINCE_UNPLUGGED) {
+ val -= mUnpluggedStarts;
}
return val;
@@ -4369,7 +5366,7 @@ public final class BatteryStatsImpl extends BatteryStats {
t = wl.mTimerPartial;
if (t == null) {
t = new StopwatchTimer(Uid.this, WAKE_TYPE_PARTIAL,
- mPartialTimers, mUnpluggables);
+ mPartialTimers, mOnBatteryScreenOffTimeBase);
wl.mTimerPartial = t;
}
return t;
@@ -4377,7 +5374,7 @@ public final class BatteryStatsImpl extends BatteryStats {
t = wl.mTimerFull;
if (t == null) {
t = new StopwatchTimer(Uid.this, WAKE_TYPE_FULL,
- mFullTimers, mUnpluggables);
+ mFullTimers, mOnBatteryTimeBase);
wl.mTimerFull = t;
}
return t;
@@ -4385,7 +5382,7 @@ public final class BatteryStatsImpl extends BatteryStats {
t = wl.mTimerWindow;
if (t == null) {
t = new StopwatchTimer(Uid.this, WAKE_TYPE_WINDOW,
- mWindowTimers, mUnpluggables);
+ mWindowTimers, mOnBatteryTimeBase);
wl.mTimerWindow = t;
}
return t;
@@ -4412,34 +5409,36 @@ public final class BatteryStatsImpl extends BatteryStats {
timers = new ArrayList<StopwatchTimer>();
mSensorTimers.put(sensor, timers);
}
- t = new StopwatchTimer(Uid.this, BatteryStats.SENSOR, timers, mUnpluggables);
+ t = new StopwatchTimer(Uid.this, BatteryStats.SENSOR, timers, mOnBatteryTimeBase);
se.mTimer = t;
return t;
}
- public void noteStartWakeLocked(int pid, String name, int type) {
+ public void noteStartWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) {
StopwatchTimer t = getWakeTimerLocked(name, type);
if (t != null) {
- t.startRunningLocked(BatteryStatsImpl.this);
+ t.startRunningLocked(elapsedRealtimeMs);
}
if (pid >= 0 && type == WAKE_TYPE_PARTIAL) {
Pid p = getPidStatsLocked(pid);
- if (p.mWakeStart == 0) {
- p.mWakeStart = SystemClock.elapsedRealtime();
+ if (p.mWakeNesting++ == 0) {
+ p.mWakeStartMs = elapsedRealtimeMs;
}
}
}
- public void noteStopWakeLocked(int pid, String name, int type) {
+ public void noteStopWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) {
StopwatchTimer t = getWakeTimerLocked(name, type);
if (t != null) {
- t.stopRunningLocked(BatteryStatsImpl.this);
+ t.stopRunningLocked(elapsedRealtimeMs);
}
if (pid >= 0 && type == WAKE_TYPE_PARTIAL) {
Pid p = mPids.get(pid);
- if (p != null && p.mWakeStart != 0) {
- p.mWakeSum += SystemClock.elapsedRealtime() - p.mWakeStart;
- p.mWakeStart = 0;
+ if (p != null && p.mWakeNesting > 0) {
+ if (p.mWakeNesting-- == 1) {
+ p.mWakeSumMs += elapsedRealtimeMs - p.mWakeStartMs;
+ p.mWakeStartMs = 0;
+ }
}
}
}
@@ -4458,32 +5457,32 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- public void noteStartSensor(int sensor) {
+ public void noteStartSensor(int sensor, long elapsedRealtimeMs) {
StopwatchTimer t = getSensorTimerLocked(sensor, true);
if (t != null) {
- t.startRunningLocked(BatteryStatsImpl.this);
+ t.startRunningLocked(elapsedRealtimeMs);
}
}
- public void noteStopSensor(int sensor) {
+ public void noteStopSensor(int sensor, long elapsedRealtimeMs) {
// Don't create a timer if one doesn't already exist
StopwatchTimer t = getSensorTimerLocked(sensor, false);
if (t != null) {
- t.stopRunningLocked(BatteryStatsImpl.this);
+ t.stopRunningLocked(elapsedRealtimeMs);
}
}
- public void noteStartGps() {
+ public void noteStartGps(long elapsedRealtimeMs) {
StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, true);
if (t != null) {
- t.startRunningLocked(BatteryStatsImpl.this);
+ t.startRunningLocked(elapsedRealtimeMs);
}
}
- public void noteStopGps() {
+ public void noteStopGps(long elapsedRealtimeMs) {
StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, false);
if (t != null) {
- t.stopRunningLocked(BatteryStatsImpl.this);
+ t.stopRunningLocked(elapsedRealtimeMs);
}
}
@@ -4496,38 +5495,50 @@ public final class BatteryStatsImpl extends BatteryStats {
mFile = new JournaledFile(new File(filename), new File(filename + ".tmp"));
mHandler = new MyHandler(handler.getLooper());
mStartCount++;
- mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables);
+ mScreenOnTimer = new StopwatchTimer(null, -1, null, mOnBatteryTimeBase);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mUnpluggables);
+ mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mOnBatteryTimeBase);
}
- mInputEventCounter = new Counter(mUnpluggables);
- mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables);
+ mInputEventCounter = new Counter(mOnBatteryTimeBase);
+ mPhoneOnTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
- mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, null, mUnpluggables);
+ mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, null,
+ mOnBatteryTimeBase);
}
- mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables);
+ mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mOnBatteryTimeBase);
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
- mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i, null, mUnpluggables);
+ mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i, null,
+ mOnBatteryTimeBase);
}
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables);
- }
- mWifiOnTimer = new StopwatchTimer(null, -3, null, mUnpluggables);
- mGlobalWifiRunningTimer = new StopwatchTimer(null, -4, null, mUnpluggables);
- mBluetoothOnTimer = new StopwatchTimer(null, -5, null, mUnpluggables);
- mAudioOnTimer = new StopwatchTimer(null, -6, null, mUnpluggables);
- mVideoOnTimer = new StopwatchTimer(null, -7, null, mUnpluggables);
+ mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+ mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+ }
+ mMobileRadioActiveTimer = new StopwatchTimer(null, -400, null, mOnBatteryTimeBase);
+ mMobileRadioActivePerAppTimer = new StopwatchTimer(null, -401, null, mOnBatteryTimeBase);
+ mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase);
+ mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase);
+ mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase);
+ mWifiOnTimer = new StopwatchTimer(null, -3, null, mOnBatteryTimeBase);
+ mGlobalWifiRunningTimer = new StopwatchTimer(null, -4, null, mOnBatteryTimeBase);
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ mWifiStateTimer[i] = new StopwatchTimer(null, -600-i, null, mOnBatteryTimeBase);
+ }
+ mBluetoothOnTimer = new StopwatchTimer(null, -5, null, mOnBatteryTimeBase);
+ for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
+ mBluetoothStateTimer[i] = new StopwatchTimer(null, -500-i, null, mOnBatteryTimeBase);
+ }
+ mAudioOnTimer = new StopwatchTimer(null, -6, null, mOnBatteryTimeBase);
+ mVideoOnTimer = new StopwatchTimer(null, -7, null, mOnBatteryTimeBase);
mOnBattery = mOnBatteryInternal = false;
- initTimes();
- mTrackBatteryPastUptime = 0;
- mTrackBatteryPastRealtime = 0;
- mUptimeStart = mTrackBatteryUptimeStart = SystemClock.uptimeMillis() * 1000;
- mRealtimeStart = mTrackBatteryRealtimeStart = SystemClock.elapsedRealtime() * 1000;
- mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart);
- mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart);
+ long uptime = SystemClock.uptimeMillis() * 1000;
+ long realtime = SystemClock.elapsedRealtime() * 1000;
+ initTimes(uptime, realtime);
mDischargeStartLevel = 0;
mDischargeUnplugLevel = 0;
+ mDischargePlugLevel = -1;
mDischargeCurrentLevel = 0;
+ mCurrentBatteryLevel = 0;
initDischarge();
clearHistoryLocked();
}
@@ -4557,18 +5568,21 @@ public final class BatteryStatsImpl extends BatteryStats {
public boolean startIteratingOldHistoryLocked() {
if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize()
+ " pos=" + mHistoryBuffer.dataPosition());
+ if ((mHistoryIterator = mHistory) == null) {
+ return false;
+ }
mHistoryBuffer.setDataPosition(0);
mHistoryReadTmp.clear();
mReadOverflow = false;
mIteratingHistory = true;
- return (mHistoryIterator = mHistory) != null;
+ return true;
}
@Override
public boolean getNextOldHistoryLocked(HistoryItem out) {
boolean end = mHistoryBuffer.dataPosition() >= mHistoryBuffer.dataSize();
if (!end) {
- mHistoryReadTmp.readDelta(mHistoryBuffer);
+ readHistoryDelta(mHistoryBuffer, mHistoryReadTmp);
mReadOverflow |= mHistoryReadTmp.cmd == HistoryItem.CMD_OVERFLOW;
}
HistoryItem cur = mHistoryIterator;
@@ -4584,13 +5598,13 @@ public final class BatteryStatsImpl extends BatteryStats {
if (end) {
Slog.w(TAG, "New history ends before old history!");
} else if (!out.same(mHistoryReadTmp)) {
- long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
PrintWriter pw = new FastPrintWriter(new LogWriter(android.util.Log.WARN, TAG));
pw.println("Histories differ!");
pw.println("Old history:");
- (new HistoryPrinter()).printNextItem(pw, out, now);
+ (new HistoryPrinter()).printNextItem(pw, out, 0, false, true);
pw.println("New history:");
- (new HistoryPrinter()).printNextItem(pw, mHistoryReadTmp, now);
+ (new HistoryPrinter()).printNextItem(pw, mHistoryReadTmp, 0, false,
+ true);
pw.flush();
}
}
@@ -4601,16 +5615,60 @@ public final class BatteryStatsImpl extends BatteryStats {
public void finishIteratingOldHistoryLocked() {
mIteratingHistory = false;
mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+ mHistoryIterator = null;
+ }
+
+ public int getHistoryTotalSize() {
+ return MAX_HISTORY_BUFFER;
+ }
+
+ public int getHistoryUsedSize() {
+ return mHistoryBuffer.dataSize();
}
@Override
public boolean startIteratingHistoryLocked() {
if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize()
+ " pos=" + mHistoryBuffer.dataPosition());
+ if (mHistoryBuffer.dataSize() <= 0) {
+ return false;
+ }
mHistoryBuffer.setDataPosition(0);
mReadOverflow = false;
mIteratingHistory = true;
- return mHistoryBuffer.dataSize() > 0;
+ mReadHistoryStrings = new String[mHistoryTagPool.size()];
+ mReadHistoryUids = new int[mHistoryTagPool.size()];
+ mReadHistoryChars = 0;
+ for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
+ final HistoryTag tag = ent.getKey();
+ final int idx = ent.getValue();
+ mReadHistoryStrings[idx] = tag.string;
+ mReadHistoryUids[idx] = tag.uid;
+ mReadHistoryChars += tag.string.length() + 1;
+ }
+ return true;
+ }
+
+ @Override
+ public int getHistoryStringPoolSize() {
+ return mReadHistoryStrings.length;
+ }
+
+ @Override
+ public int getHistoryStringPoolBytes() {
+ // Each entry is a fixed 12 bytes: 4 for index, 4 for uid, 4 for string size
+ // Each string character is 2 bytes.
+ return (mReadHistoryStrings.length * 12) + (mReadHistoryChars * 2);
+ }
+
+ @Override
+ public String getHistoryTagPoolString(int index) {
+ return mReadHistoryStrings[index];
+ }
+
+ @Override
+ public int getHistoryTagPoolUid(int index) {
+ return mReadHistoryUids[index];
}
@Override
@@ -4624,7 +5682,12 @@ public final class BatteryStatsImpl extends BatteryStats {
return false;
}
- out.readDelta(mHistoryBuffer);
+ final long lastRealtime = out.time;
+ final long lastWalltime = out.currentTime;
+ readHistoryDelta(mHistoryBuffer, out);
+ if (out.cmd != HistoryItem.CMD_CURRENT_TIME && lastWalltime != 0) {
+ out.currentTime = lastWalltime + (out.time - lastRealtime);
+ }
return true;
}
@@ -4632,6 +5695,7 @@ public final class BatteryStatsImpl extends BatteryStats {
public void finishIteratingHistoryLocked() {
mIteratingHistory = false;
mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+ mReadHistoryStrings = null;
}
@Override
@@ -4652,13 +5716,14 @@ public final class BatteryStatsImpl extends BatteryStats {
return mScreenOn;
}
- void initTimes() {
- mBatteryRealtime = mTrackBatteryPastUptime = 0;
- mBatteryUptime = mTrackBatteryPastRealtime = 0;
- mUptimeStart = mTrackBatteryUptimeStart = SystemClock.uptimeMillis() * 1000;
- mRealtimeStart = mTrackBatteryRealtimeStart = SystemClock.elapsedRealtime() * 1000;
- mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart);
- mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart);
+ void initTimes(long uptime, long realtime) {
+ mStartClockTime = System.currentTimeMillis();
+ mOnBatteryTimeBase.init(uptime, realtime);
+ mOnBatteryScreenOffTimeBase.init(uptime, realtime);
+ mRealtime = 0;
+ mUptime = 0;
+ mRealtimeStart = realtime;
+ mUptimeStart = uptime;
}
void initDischarge() {
@@ -4668,32 +5733,75 @@ public final class BatteryStatsImpl extends BatteryStats {
mDischargeAmountScreenOnSinceCharge = 0;
mDischargeAmountScreenOff = 0;
mDischargeAmountScreenOffSinceCharge = 0;
+ mLastDischargeStepTime = -1;
+ mNumDischargeStepDurations = 0;
+ mLastChargeStepTime = -1;
+ mNumChargeStepDurations = 0;
}
-
- public void resetAllStatsLocked() {
+
+ public void resetAllStatsCmdLocked() {
+ resetAllStatsLocked();
+ final long mSecUptime = SystemClock.uptimeMillis();
+ long uptime = mSecUptime * 1000;
+ long mSecRealtime = SystemClock.elapsedRealtime();
+ long realtime = mSecRealtime * 1000;
+ mDischargeStartLevel = mHistoryCur.batteryLevel;
+ pullPendingStateUpdatesLocked();
+ addHistoryRecordLocked(mSecRealtime, mSecUptime);
+ mDischargeCurrentLevel = mDischargeUnplugLevel = mDischargePlugLevel
+ = mCurrentBatteryLevel = mHistoryCur.batteryLevel;
+ mOnBatteryTimeBase.reset(uptime, realtime);
+ mOnBatteryScreenOffTimeBase.reset(uptime, realtime);
+ if ((mHistoryCur.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) == 0) {
+ if (mScreenOn) {
+ mDischargeScreenOnUnplugLevel = mHistoryCur.batteryLevel;
+ mDischargeScreenOffUnplugLevel = 0;
+ } else {
+ mDischargeScreenOnUnplugLevel = 0;
+ mDischargeScreenOffUnplugLevel = mHistoryCur.batteryLevel;
+ }
+ mDischargeAmountScreenOn = 0;
+ mDischargeAmountScreenOff = 0;
+ }
+ initActiveHistoryEventsLocked(mSecRealtime, mSecUptime);
+ }
+
+ private void resetAllStatsLocked() {
mStartCount = 0;
- initTimes();
- mScreenOnTimer.reset(this, false);
+ initTimes(SystemClock.uptimeMillis() * 1000, SystemClock.elapsedRealtime() * 1000);
+ mScreenOnTimer.reset(false);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- mScreenBrightnessTimer[i].reset(this, false);
+ mScreenBrightnessTimer[i].reset(false);
}
mInputEventCounter.reset(false);
- mPhoneOnTimer.reset(this, false);
- mAudioOnTimer.reset(this, false);
- mVideoOnTimer.reset(this, false);
+ mPhoneOnTimer.reset(false);
+ mAudioOnTimer.reset(false);
+ mVideoOnTimer.reset(false);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
- mPhoneSignalStrengthsTimer[i].reset(this, false);
+ mPhoneSignalStrengthsTimer[i].reset(false);
}
- mPhoneSignalScanningTimer.reset(this, false);
+ mPhoneSignalScanningTimer.reset(false);
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
- mPhoneDataConnectionsTimer[i].reset(this, false);
+ mPhoneDataConnectionsTimer[i].reset(false);
}
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i].reset(false);
+ mNetworkByteActivityCounters[i].reset(false);
+ mNetworkPacketActivityCounters[i].reset(false);
+ }
+ mMobileRadioActiveTimer.reset(false);
+ mMobileRadioActivePerAppTimer.reset(false);
+ mMobileRadioActiveAdjustedTime.reset(false);
+ mMobileRadioActiveUnknownTime.reset(false);
+ mMobileRadioActiveUnknownCount.reset(false);
+ mWifiOnTimer.reset(false);
+ mGlobalWifiRunningTimer.reset(false);
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ mWifiStateTimer[i].reset(false);
+ }
+ mBluetoothOnTimer.reset(false);
+ for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
+ mBluetoothStateTimer[i].reset(false);
}
- mWifiOnTimer.reset(this, false);
- mGlobalWifiRunningTimer.reset(this, false);
- mBluetoothOnTimer.reset(this, false);
for (int i=0; i<mUidStats.size(); i++) {
if (mUidStats.valueAt(i).reset()) {
@@ -4704,16 +5812,41 @@ public final class BatteryStatsImpl extends BatteryStats {
if (mKernelWakelockStats.size() > 0) {
for (SamplingTimer timer : mKernelWakelockStats.values()) {
- mUnpluggables.remove(timer);
+ mOnBatteryScreenOffTimeBase.remove(timer);
}
mKernelWakelockStats.clear();
}
-
+
+ if (mWakeupReasonStats.size() > 0) {
+ for (LongSamplingCounter timer : mWakeupReasonStats.values()) {
+ mOnBatteryScreenOffTimeBase.remove(timer);
+ }
+ mWakeupReasonStats.clear();
+ }
+
initDischarge();
clearHistoryLocked();
}
+ private void initActiveHistoryEventsLocked(long elapsedRealtimeMs, long uptimeMs) {
+ for (int i=0; i<HistoryItem.EVENT_COUNT; i++) {
+ HashMap<String, SparseBooleanArray> active = mActiveEvents[i];
+ if (active == null) {
+ continue;
+ }
+ for (HashMap.Entry<String, SparseBooleanArray> ent : active.entrySet()) {
+ SparseBooleanArray uids = ent.getValue();
+ for (int j=0; j<uids.size(); j++) {
+ if (uids.valueAt(j)) {
+ addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, i, ent.getKey(),
+ uids.keyAt(j));
+ }
+ }
+ }
+ }
+ }
+
void updateDischargeScreenLevelsLocked(boolean oldScreenOn, boolean newScreenOn) {
if (oldScreenOn) {
int diff = mDischargeScreenOnUnplugLevel - mDischargeCurrentLevel;
@@ -4737,45 +5870,51 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- void setOnBattery(boolean onBattery, int oldStatus, int level) {
- synchronized(this) {
- setOnBatteryLocked(onBattery, oldStatus, level);
+ public void pullPendingStateUpdatesLocked() {
+ updateKernelWakelocksLocked();
+ updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime());
+ if (mOnBatteryInternal) {
+ updateDischargeScreenLevelsLocked(mScreenOn, mScreenOn);
}
}
- void setOnBatteryLocked(boolean onBattery, int oldStatus, int level) {
+ void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery,
+ final int oldStatus, final int level) {
boolean doWrite = false;
Message m = mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE);
m.arg1 = onBattery ? 1 : 0;
mHandler.sendMessage(m);
mOnBattery = mOnBatteryInternal = onBattery;
- long uptime = SystemClock.uptimeMillis() * 1000;
- long mSecRealtime = SystemClock.elapsedRealtime();
- long realtime = mSecRealtime * 1000;
+ final long uptime = mSecUptime * 1000;
+ final long realtime = mSecRealtime * 1000;
if (onBattery) {
// We will reset our status if we are unplugging after the
// battery was last full, or the level is at 100, or
// we have gone through a significant charge (from a very low
// level to a now very high level).
+ boolean reset = false;
if (oldStatus == BatteryManager.BATTERY_STATUS_FULL
|| level >= 90
|| (mDischargeCurrentLevel < 20 && level >= 80)) {
doWrite = true;
resetAllStatsLocked();
mDischargeStartLevel = level;
+ reset = true;
+ mNumDischargeStepDurations = 0;
}
- updateKernelWakelocksLocked();
- updateNetworkActivityLocked();
+ mLastDischargeStepLevel = level;
+ mLastDischargeStepTime = -1;
+ pullPendingStateUpdatesLocked();
mHistoryCur.batteryLevel = (byte)level;
mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Battery unplugged to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(mSecRealtime);
- mTrackBatteryUptimeStart = uptime;
- mTrackBatteryRealtimeStart = realtime;
- mUnpluggedBatteryUptime = getBatteryUptimeLocked(uptime);
- mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(realtime);
+ if (reset) {
+ mRecordingHistory = true;
+ startRecordingHistory(mSecRealtime, mSecUptime, reset);
+ }
+ addHistoryRecordLocked(mSecRealtime, mSecUptime);
mDischargeCurrentLevel = mDischargeUnplugLevel = level;
if (mScreenOn) {
mDischargeScreenOnUnplugLevel = level;
@@ -4786,24 +5925,24 @@ public final class BatteryStatsImpl extends BatteryStats {
}
mDischargeAmountScreenOn = 0;
mDischargeAmountScreenOff = 0;
- doUnplugLocked(realtime, mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);
+ updateTimeBasesLocked(true, !mScreenOn, uptime, realtime);
} else {
- updateKernelWakelocksLocked();
- updateNetworkActivityLocked();
+ pullPendingStateUpdatesLocked();
mHistoryCur.batteryLevel = (byte)level;
mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(mSecRealtime);
- mTrackBatteryPastUptime += uptime - mTrackBatteryUptimeStart;
- mTrackBatteryPastRealtime += realtime - mTrackBatteryRealtimeStart;
- mDischargeCurrentLevel = level;
+ addHistoryRecordLocked(mSecRealtime, mSecUptime);
+ mDischargeCurrentLevel = mDischargePlugLevel = level;
if (level < mDischargeUnplugLevel) {
mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1;
mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level;
}
updateDischargeScreenLevelsLocked(mScreenOn, mScreenOn);
- doPlugLocked(realtime, getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime));
+ updateTimeBasesLocked(false, !mScreenOn, uptime, realtime);
+ mNumChargeStepDurations = 0;
+ mLastChargeStepLevel = level;
+ mLastChargeStepTime = -1;
}
if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) {
if (mFile != null) {
@@ -4812,13 +5951,45 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ private void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs,
+ boolean reset) {
+ mRecordingHistory = true;
+ mHistoryCur.currentTime = System.currentTimeMillis();
+ addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_CURRENT_TIME,
+ mHistoryCur);
+ mHistoryCur.currentTime = 0;
+ if (reset) {
+ initActiveHistoryEventsLocked(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
// This should probably be exposed in the API, though it's not critical
private static final int BATTERY_PLUGGED_NONE = 0;
+ private static int addLevelSteps(long[] steps, int stepCount, long lastStepTime,
+ int numStepLevels, long elapsedRealtime) {
+ if (lastStepTime >= 0 && numStepLevels > 0) {
+ long duration = elapsedRealtime - lastStepTime;
+ for (int i=0; i<numStepLevels; i++) {
+ System.arraycopy(steps, 0, steps, 1, steps.length-1);
+ long thisDuration = duration / (numStepLevels-i);
+ duration -= thisDuration;
+ steps[0] = thisDuration;
+ }
+ stepCount += numStepLevels;
+ if (stepCount > steps.length) {
+ stepCount = steps.length;
+ }
+ }
+ return stepCount;
+ }
+
public void setBatteryState(int status, int health, int plugType, int level,
int temp, int volt) {
synchronized(this) {
- boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
+ final boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
+ final long uptime = SystemClock.uptimeMillis();
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
int oldStatus = mHistoryCur.batteryStatus;
if (!mHaveBatteryLevel) {
mHaveBatteryLevel = true;
@@ -4837,7 +6008,19 @@ public final class BatteryStatsImpl extends BatteryStats {
}
if (onBattery) {
mDischargeCurrentLevel = level;
- mRecordingHistory = true;
+ if (!mRecordingHistory) {
+ mRecordingHistory = true;
+ startRecordingHistory(elapsedRealtime, uptime, true);
+ }
+ } else if (level < 96) {
+ if (!mRecordingHistory) {
+ mRecordingHistory = true;
+ startRecordingHistory(elapsedRealtime, uptime, true);
+ }
+ }
+ mCurrentBatteryLevel = level;
+ if (mDischargePlugLevel < 0) {
+ mDischargePlugLevel = level;
}
if (onBattery != mOnBattery) {
mHistoryCur.batteryLevel = (byte)level;
@@ -4846,7 +6029,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mHistoryCur.batteryPlugType = (byte)plugType;
mHistoryCur.batteryTemperature = (short)temp;
mHistoryCur.batteryVoltage = (char)volt;
- setOnBatteryLocked(onBattery, oldStatus, level);
+ setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level);
} else {
boolean changed = false;
if (mHistoryCur.batteryLevel != level) {
@@ -4876,13 +6059,30 @@ public final class BatteryStatsImpl extends BatteryStats {
changed = true;
}
if (changed) {
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime, uptime);
+ }
+ if (onBattery) {
+ if (mLastDischargeStepLevel != level) {
+ mNumDischargeStepDurations = addLevelSteps(mDischargeStepDurations,
+ mNumDischargeStepDurations, mLastDischargeStepTime,
+ mLastDischargeStepLevel - level, elapsedRealtime);
+ mLastDischargeStepLevel = level;
+ mLastDischargeStepTime = elapsedRealtime;
+ }
+ } else {
+ if (mLastChargeStepLevel != level) {
+ mNumChargeStepDurations = addLevelSteps(mChargeStepDurations,
+ mNumChargeStepDurations, mLastChargeStepTime,
+ level - mLastChargeStepLevel, elapsedRealtime);
+ mLastChargeStepLevel = level;
+ mLastChargeStepTime = elapsedRealtime;
+ }
}
}
if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) {
// We don't record history while we are plugged in and fully charged.
// The next time we are unplugged, history will be cleared.
- mRecordingHistory = false;
+ mRecordingHistory = DEBUG;
}
}
}
@@ -4902,8 +6102,8 @@ public final class BatteryStatsImpl extends BatteryStats {
SamplingTimer kwlt = mKernelWakelockStats.get(name);
if (kwlt == null) {
- kwlt = new SamplingTimer(mUnpluggables, mOnBatteryInternal,
- true /* track reported values */);
+ kwlt = new SamplingTimer(mOnBatteryScreenOffTimeBase,
+ true /* track reported val */);
mKernelWakelockStats.put(name, kwlt);
}
kwlt.updateCurrentReportedCount(kws.mCount);
@@ -4922,48 +6122,124 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- private void updateNetworkActivityLocked() {
+ static final int NET_UPDATE_MOBILE = 1<<0;
+ static final int NET_UPDATE_WIFI = 1<<1;
+ static final int NET_UPDATE_ALL = 0xffff;
+
+ private void updateNetworkActivityLocked(int which, long elapsedRealtimeMs) {
if (!SystemProperties.getBoolean(PROP_QTAGUID_ENABLED, false)) return;
- final NetworkStats snapshot;
- try {
- snapshot = mNetworkStatsFactory.readNetworkStatsDetail();
- } catch (IOException e) {
- Log.wtf(TAG, "Failed to read network stats", e);
- return;
- }
+ if ((which&NET_UPDATE_MOBILE) != 0 && mMobileIfaces.length > 0) {
+ final NetworkStats snapshot;
+ final NetworkStats last = mCurMobileSnapshot;
+ try {
+ snapshot = mNetworkStatsFactory.readNetworkStatsDetail(UID_ALL,
+ mMobileIfaces, NetworkStats.TAG_NONE, mLastMobileSnapshot);
+ } catch (IOException e) {
+ Log.wtf(TAG, "Failed to read mobile network stats", e);
+ return;
+ }
- if (mLastSnapshot == null) {
- mLastSnapshot = snapshot;
- return;
- }
+ mCurMobileSnapshot = snapshot;
+ mLastMobileSnapshot = last;
- final NetworkStats delta = snapshot.subtract(mLastSnapshot);
- mLastSnapshot = snapshot;
+ if (mOnBatteryInternal) {
+ final NetworkStats delta = NetworkStats.subtract(snapshot, last,
+ null, null, mTmpNetworkStats);
+ mTmpNetworkStats = delta;
+
+ long radioTime = mMobileRadioActivePerAppTimer.checkpointRunningLocked(
+ elapsedRealtimeMs);
+ long totalPackets = delta.getTotalPackets();
+
+ final int size = delta.size();
+ for (int i = 0; i < size; i++) {
+ final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry);
+
+ if (entry.rxBytes == 0 || entry.txBytes == 0) continue;
+
+ final Uid u = getUidStatsLocked(mapUid(entry.uid));
+ u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.rxBytes,
+ entry.rxPackets);
+ u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_DATA, entry.txBytes,
+ entry.txPackets);
+
+ if (radioTime > 0) {
+ // Distribute total radio active time in to this app.
+ long appPackets = entry.rxPackets + entry.txPackets;
+ long appRadioTime = (radioTime*appPackets)/totalPackets;
+ u.noteMobileRadioActiveTimeLocked(appRadioTime);
+ // Remove this app from the totals, so that we don't lose any time
+ // due to rounding.
+ radioTime -= appRadioTime;
+ totalPackets -= appPackets;
+ }
- NetworkStats.Entry entry = null;
- final int size = delta.size();
- for (int i = 0; i < size; i++) {
- entry = delta.getValues(i, entry);
+ mNetworkByteActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
+ entry.rxBytes);
+ mNetworkByteActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
+ entry.txBytes);
+ mNetworkPacketActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
+ entry.rxPackets);
+ mNetworkPacketActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
+ entry.txPackets);
+ }
- if (entry.rxBytes == 0 || entry.txBytes == 0) continue;
- if (entry.tag != NetworkStats.TAG_NONE) continue;
+ if (radioTime > 0) {
+ // Whoops, there is some radio time we can't blame on an app!
+ mMobileRadioActiveUnknownTime.addCountLocked(radioTime);
+ mMobileRadioActiveUnknownCount.addCountLocked(1);
+ }
+ }
+ }
- final Uid u = getUidStatsLocked(entry.uid);
+ if ((which&NET_UPDATE_WIFI) != 0 && mWifiIfaces.length > 0) {
+ final NetworkStats snapshot;
+ final NetworkStats last = mCurWifiSnapshot;
+ try {
+ snapshot = mNetworkStatsFactory.readNetworkStatsDetail(UID_ALL,
+ mWifiIfaces, NetworkStats.TAG_NONE, mLastWifiSnapshot);
+ } catch (IOException e) {
+ Log.wtf(TAG, "Failed to read wifi network stats", e);
+ return;
+ }
- if (mMobileIfaces.contains(entry.iface)) {
- u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_BYTES, entry.rxBytes);
- u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_BYTES, entry.txBytes);
+ mCurWifiSnapshot = snapshot;
+ mLastWifiSnapshot = last;
- mNetworkActivityCounters[NETWORK_MOBILE_RX_BYTES].addCountLocked(entry.rxBytes);
- mNetworkActivityCounters[NETWORK_MOBILE_TX_BYTES].addCountLocked(entry.txBytes);
+ if (mOnBatteryInternal) {
+ final NetworkStats delta = NetworkStats.subtract(snapshot, last,
+ null, null, mTmpNetworkStats);
+ mTmpNetworkStats = delta;
+
+ final int size = delta.size();
+ for (int i = 0; i < size; i++) {
+ final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry);
+
+ if (DEBUG) {
+ final NetworkStats.Entry cur = snapshot.getValues(i, null);
+ Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes
+ + " tx=" + entry.txBytes + ", cur rx=" + cur.rxBytes
+ + " tx=" + cur.txBytes);
+ }
+
+ if (entry.rxBytes == 0 || entry.txBytes == 0) continue;
- } else if (mWifiIfaces.contains(entry.iface)) {
- u.noteNetworkActivityLocked(NETWORK_WIFI_RX_BYTES, entry.rxBytes);
- u.noteNetworkActivityLocked(NETWORK_WIFI_TX_BYTES, entry.txBytes);
+ final Uid u = getUidStatsLocked(mapUid(entry.uid));
+ u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes,
+ entry.rxPackets);
+ u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes,
+ entry.txPackets);
- mNetworkActivityCounters[NETWORK_WIFI_RX_BYTES].addCountLocked(entry.rxBytes);
- mNetworkActivityCounters[NETWORK_WIFI_TX_BYTES].addCountLocked(entry.txBytes);
+ mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
+ entry.rxBytes);
+ mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
+ entry.txBytes);
+ mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
+ entry.rxPackets);
+ mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
+ entry.txPackets);
+ }
}
}
}
@@ -4980,9 +6256,8 @@ public final class BatteryStatsImpl extends BatteryStats {
public long computeUptime(long curTime, int which) {
switch (which) {
case STATS_SINCE_CHARGED: return mUptime + (curTime-mUptimeStart);
- case STATS_LAST: return mLastUptime;
case STATS_CURRENT: return (curTime-mUptimeStart);
- case STATS_SINCE_UNPLUGGED: return (curTime-mTrackBatteryUptimeStart);
+ case STATS_SINCE_UNPLUGGED: return (curTime-mOnBatteryTimeBase.getUptimeStart());
}
return 0;
}
@@ -4991,71 +6266,155 @@ public final class BatteryStatsImpl extends BatteryStats {
public long computeRealtime(long curTime, int which) {
switch (which) {
case STATS_SINCE_CHARGED: return mRealtime + (curTime-mRealtimeStart);
- case STATS_LAST: return mLastRealtime;
case STATS_CURRENT: return (curTime-mRealtimeStart);
- case STATS_SINCE_UNPLUGGED: return (curTime-mTrackBatteryRealtimeStart);
+ case STATS_SINCE_UNPLUGGED: return (curTime-mOnBatteryTimeBase.getRealtimeStart());
}
return 0;
}
@Override
public long computeBatteryUptime(long curTime, int which) {
- switch (which) {
- case STATS_SINCE_CHARGED:
- return mBatteryUptime + getBatteryUptime(curTime);
- case STATS_LAST:
- return mBatteryLastUptime;
- case STATS_CURRENT:
- return getBatteryUptime(curTime);
- case STATS_SINCE_UNPLUGGED:
- return getBatteryUptimeLocked(curTime) - mUnpluggedBatteryUptime;
- }
- return 0;
+ return mOnBatteryTimeBase.computeUptime(curTime, which);
}
@Override
public long computeBatteryRealtime(long curTime, int which) {
- switch (which) {
- case STATS_SINCE_CHARGED:
- return mBatteryRealtime + getBatteryRealtimeLocked(curTime);
- case STATS_LAST:
- return mBatteryLastRealtime;
- case STATS_CURRENT:
- return getBatteryRealtimeLocked(curTime);
- case STATS_SINCE_UNPLUGGED:
- return getBatteryRealtimeLocked(curTime) - mUnpluggedBatteryRealtime;
+ return mOnBatteryTimeBase.computeRealtime(curTime, which);
+ }
+
+ @Override
+ public long computeBatteryScreenOffUptime(long curTime, int which) {
+ return mOnBatteryScreenOffTimeBase.computeUptime(curTime, which);
+ }
+
+ @Override
+ public long computeBatteryScreenOffRealtime(long curTime, int which) {
+ return mOnBatteryScreenOffTimeBase.computeRealtime(curTime, which);
+ }
+
+ private long computeTimePerLevel(long[] steps, int numSteps) {
+ // For now we'll do a simple average across all steps.
+ if (numSteps <= 0) {
+ return -1;
}
- return 0;
+ long total = 0;
+ for (int i=0; i<numSteps; i++) {
+ total += steps[i];
+ }
+ return total / numSteps;
+ /*
+ long[] buckets = new long[numSteps];
+ int numBuckets = 0;
+ int numToAverage = 4;
+ int i = 0;
+ while (i < numSteps) {
+ long totalTime = 0;
+ int num = 0;
+ for (int j=0; j<numToAverage && (i+j)<numSteps; j++) {
+ totalTime += steps[i+j];
+ num++;
+ }
+ buckets[numBuckets] = totalTime / num;
+ numBuckets++;
+ numToAverage *= 2;
+ i += num;
+ }
+ if (numBuckets < 1) {
+ return -1;
+ }
+ long averageTime = buckets[numBuckets-1];
+ for (i=numBuckets-2; i>=0; i--) {
+ averageTime = (averageTime + buckets[i]) / 2;
+ }
+ return averageTime;
+ */
}
- long getBatteryUptimeLocked(long curTime) {
- long time = mTrackBatteryPastUptime;
- if (mOnBatteryInternal) {
- time += curTime - mTrackBatteryUptimeStart;
+ @Override
+ public long computeBatteryTimeRemaining(long curTime) {
+ if (!mOnBattery) {
+ return -1;
+ }
+ /* Simple implementation just looks at the average discharge per level across the
+ entire sample period.
+ int discharge = (getLowDischargeAmountSinceCharge()+getHighDischargeAmountSinceCharge())/2;
+ if (discharge < 2) {
+ return -1;
}
- return time;
+ long duration = computeBatteryRealtime(curTime, STATS_SINCE_CHARGED);
+ if (duration < 1000*1000) {
+ return -1;
+ }
+ long usPerLevel = duration/discharge;
+ return usPerLevel * mCurrentBatteryLevel;
+ */
+ if (mNumDischargeStepDurations < 1) {
+ return -1;
+ }
+ long msPerLevel = computeTimePerLevel(mDischargeStepDurations, mNumDischargeStepDurations);
+ if (msPerLevel <= 0) {
+ return -1;
+ }
+ return (msPerLevel * mCurrentBatteryLevel) * 1000;
}
- long getBatteryUptimeLocked() {
- return getBatteryUptime(SystemClock.uptimeMillis() * 1000);
+ public int getNumDischargeStepDurations() {
+ return mNumDischargeStepDurations;
}
- @Override
- public long getBatteryUptime(long curTime) {
- return getBatteryUptimeLocked(curTime);
+ public long[] getDischargeStepDurationsArray() {
+ return mDischargeStepDurations;
}
- long getBatteryRealtimeLocked(long curTime) {
- long time = mTrackBatteryPastRealtime;
- if (mOnBatteryInternal) {
- time += curTime - mTrackBatteryRealtimeStart;
+ @Override
+ public long computeChargeTimeRemaining(long curTime) {
+ if (mOnBattery) {
+ // Not yet working.
+ return -1;
+ }
+ /* Broken
+ int curLevel = mCurrentBatteryLevel;
+ int plugLevel = mDischargePlugLevel;
+ if (plugLevel < 0 || curLevel < (plugLevel+1)) {
+ return -1;
}
- return time;
+ long duration = computeBatteryRealtime(curTime, STATS_SINCE_UNPLUGGED);
+ if (duration < 1000*1000) {
+ return -1;
+ }
+ long usPerLevel = duration/(curLevel-plugLevel);
+ return usPerLevel * (100-curLevel);
+ */
+ if (mNumChargeStepDurations < 1) {
+ return -1;
+ }
+ long msPerLevel = computeTimePerLevel(mChargeStepDurations, mNumChargeStepDurations);
+ if (msPerLevel <= 0) {
+ return -1;
+ }
+ return (msPerLevel * (100-mCurrentBatteryLevel)) * 1000;
+ }
+
+ public int getNumChargeStepDurations() {
+ return mNumChargeStepDurations;
+ }
+
+ public long[] getChargeStepDurationsArray() {
+ return mChargeStepDurations;
+ }
+
+ long getBatteryUptimeLocked() {
+ return mOnBatteryTimeBase.getUptime(SystemClock.uptimeMillis() * 1000);
+ }
+
+ @Override
+ public long getBatteryUptime(long curTime) {
+ return mOnBatteryTimeBase.getUptime(curTime);
}
@Override
public long getBatteryRealtime(long curTime) {
- return getBatteryRealtimeLocked(curTime);
+ return mOnBatteryTimeBase.getRealtime(curTime);
}
@Override
@@ -5101,7 +6460,18 @@ public final class BatteryStatsImpl extends BatteryStats {
return val;
}
}
-
+
+ @Override
+ public int getDischargeAmount(int which) {
+ int dischargeAmount = which == STATS_SINCE_CHARGED
+ ? getHighDischargeAmountSinceCharge()
+ : (getDischargeStartLevel() - getDischargeCurrentLevel());
+ if (dischargeAmount < 0) {
+ dischargeAmount = 0;
+ }
+ return dischargeAmount;
+ }
+
public int getDischargeAmountScreenOn() {
synchronized(this) {
int val = mDischargeAmountScreenOn;
@@ -5175,24 +6545,7 @@ public final class BatteryStatsImpl extends BatteryStats {
* if needed.
*/
public Uid.Proc getProcessStatsLocked(int uid, String name) {
- Uid u = getUidStatsLocked(uid);
- return u.getProcessStatsLocked(name);
- }
-
- /**
- * Retrieve the statistics object for a particular process, given
- * the name of the process.
- * @param name process name
- * @return the statistics object for the process
- */
- public Uid.Proc getProcessStatsLocked(String name, int pid) {
- int uid;
- if (mUidCache.containsKey(name)) {
- uid = mUidCache.get(name);
- } else {
- uid = Process.getUidForPid(pid);
- mUidCache.put(name, uid);
- }
+ uid = mapUid(uid);
Uid u = getUidStatsLocked(uid);
return u.getProcessStatsLocked(name);
}
@@ -5202,6 +6555,7 @@ public final class BatteryStatsImpl extends BatteryStats {
* if needed.
*/
public Uid.Pkg getPackageStatsLocked(int uid, String pkg) {
+ uid = mapUid(uid);
Uid u = getUidStatsLocked(uid);
return u.getPackageStatsLocked(pkg);
}
@@ -5211,6 +6565,7 @@ public final class BatteryStatsImpl extends BatteryStats {
* if needed.
*/
public Uid.Pkg.Serv getServiceStatsLocked(int uid, String pkg, String name) {
+ uid = mapUid(uid);
Uid u = getUidStatsLocked(uid);
return u.getServiceStatsLocked(pkg, name);
}
@@ -5251,7 +6606,7 @@ public final class BatteryStatsImpl extends BatteryStats {
time = (time*uidRunningTime)/totalRunningTime;
SamplingCounter uidSc = uidProc.mSpeedBins[sb];
if (uidSc == null) {
- uidSc = new SamplingCounter(mUnpluggables);
+ uidSc = new SamplingCounter(mOnBatteryTimeBase);
uidProc.mSpeedBins[sb] = uidSc;
}
uidSc.mCount.addAndGet((int)time);
@@ -5388,15 +6743,20 @@ public final class BatteryStatsImpl extends BatteryStats {
stream.close();
readSummaryFromParcel(in);
- } catch(java.io.IOException e) {
+ } catch(Exception e) {
Slog.e("BatteryStats", "Error reading battery statistics", e);
}
- long now = SystemClock.elapsedRealtime();
- if (USE_OLD_HISTORY) {
- addHistoryRecordLocked(now, HistoryItem.CMD_START);
+ if (mHistoryBuffer.dataPosition() > 0) {
+ mRecordingHistory = true;
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ if (USE_OLD_HISTORY) {
+ addHistoryRecordLocked(elapsedRealtime, uptime, HistoryItem.CMD_START, mHistoryCur);
+ }
+ addHistoryBufferLocked(elapsedRealtime, uptime, HistoryItem.CMD_START, mHistoryCur);
+ startRecordingHistory(elapsedRealtime, uptime, false);
}
- addHistoryBufferLocked(now, HistoryItem.CMD_START);
}
public int describeContents() {
@@ -5408,6 +6768,25 @@ public final class BatteryStatsImpl extends BatteryStats {
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
+ mHistoryTagPool.clear();
+ mNextHistoryTagIdx = 0;
+ mNumHistoryTagChars = 0;
+
+ int numTags = in.readInt();
+ for (int i=0; i<numTags; i++) {
+ int idx = in.readInt();
+ String str = in.readString();
+ int uid = in.readInt();
+ HistoryTag tag = new HistoryTag();
+ tag.string = str;
+ tag.uid = uid;
+ tag.poolIdx = idx;
+ mHistoryTagPool.put(tag, idx);
+ if (idx >= mNextHistoryTagIdx) {
+ mNextHistoryTagIdx = idx+1;
+ }
+ mNumHistoryTagChars += tag.string.length() + 1;
+ }
int bufSize = in.readInt();
int curPos = in.dataPosition();
@@ -5471,11 +6850,18 @@ public final class BatteryStatsImpl extends BatteryStats {
StringBuilder sb = new StringBuilder(128);
sb.append("****************** WRITING mHistoryBaseTime: ");
TimeUtils.formatDuration(mHistoryBaseTime, sb);
- sb.append(" mLastHistoryTime: ");
- TimeUtils.formatDuration(mLastHistoryTime, sb);
+ sb.append(" mLastHistoryElapsedRealtime: ");
+ TimeUtils.formatDuration(mLastHistoryElapsedRealtime, sb);
Slog.i(TAG, sb.toString());
}
- out.writeLong(mHistoryBaseTime + mLastHistoryTime);
+ out.writeLong(mHistoryBaseTime + mLastHistoryElapsedRealtime);
+ out.writeInt(mHistoryTagPool.size());
+ for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
+ HistoryTag tag = ent.getKey();
+ out.writeInt(ent.getValue());
+ out.writeString(tag.string);
+ out.writeInt(tag.uid);
+ }
out.writeInt(mHistoryBuffer.dataSize());
if (DEBUG_HISTORY) Slog.i(TAG, "***************** WRITING HISTORY: "
+ mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition());
@@ -5509,16 +6895,23 @@ public final class BatteryStatsImpl extends BatteryStats {
readHistory(in, true);
mStartCount = in.readInt();
- mBatteryUptime = in.readLong();
- mBatteryRealtime = in.readLong();
mUptime = in.readLong();
mRealtime = in.readLong();
+ mStartClockTime = in.readLong();
+ mOnBatteryTimeBase.readSummaryFromParcel(in);
+ mOnBatteryScreenOffTimeBase.readSummaryFromParcel(in);
mDischargeUnplugLevel = in.readInt();
+ mDischargePlugLevel = in.readInt();
mDischargeCurrentLevel = in.readInt();
+ mCurrentBatteryLevel = in.readInt();
mLowDischargeAmountSinceCharge = in.readInt();
mHighDischargeAmountSinceCharge = in.readInt();
mDischargeAmountScreenOnSinceCharge = in.readInt();
mDischargeAmountScreenOffSinceCharge = in.readInt();
+ mNumDischargeStepDurations = in.readInt();
+ in.readLongArray(mDischargeStepDurations);
+ mNumChargeStepDurations = in.readInt();
+ in.readLongArray(mChargeStepDurations);
mStartCount++;
@@ -5538,14 +6931,27 @@ public final class BatteryStatsImpl extends BatteryStats {
mPhoneDataConnectionsTimer[i].readSummaryFromParcelLocked(in);
}
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i].readSummaryFromParcelLocked(in);
- }
+ mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in);
+ mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in);
+ }
+ mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+ mMobileRadioActiveTimer.readSummaryFromParcelLocked(in);
+ mMobileRadioActivePerAppTimer.readSummaryFromParcelLocked(in);
+ mMobileRadioActiveAdjustedTime.readSummaryFromParcelLocked(in);
+ mMobileRadioActiveUnknownTime.readSummaryFromParcelLocked(in);
+ mMobileRadioActiveUnknownCount.readSummaryFromParcelLocked(in);
mWifiOn = false;
mWifiOnTimer.readSummaryFromParcelLocked(in);
mGlobalWifiRunning = false;
mGlobalWifiRunningTimer.readSummaryFromParcelLocked(in);
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ mWifiStateTimer[i].readSummaryFromParcelLocked(in);
+ }
mBluetoothOn = false;
mBluetoothOnTimer.readSummaryFromParcelLocked(in);
+ for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
+ mBluetoothStateTimer[i].readSummaryFromParcelLocked(in);
+ }
int NKW = in.readInt();
if (NKW > 10000) {
@@ -5559,7 +6965,22 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ int NWR = in.readInt();
+ if (NWR > 10000) {
+ Slog.w(TAG, "File corrupt: too many wakeup reasons " + NWR);
+ return;
+ }
+ for (int iwr = 0; iwr < NWR; iwr++) {
+ if (in.readInt() != 0) {
+ String reasonName = in.readString();
+ getWakeupReasonCounterLocked(reasonName).readSummaryFromParcelLocked(in);
+ }
+ }
+
sNumSpeedSteps = in.readInt();
+ if (sNumSpeedSteps < 0 || sNumSpeedSteps > 100) {
+ throw new BadParcelableException("Bad speed steps in data: " + sNumSpeedSteps);
+ }
final int NU = in.readInt();
if (NU > 10000) {
@@ -5619,12 +7040,15 @@ public final class BatteryStatsImpl extends BatteryStats {
}
if (in.readInt() != 0) {
- if (u.mNetworkActivityCounters == null) {
+ if (u.mNetworkByteActivityCounters == null) {
u.initNetworkActivityLocked();
}
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- u.mNetworkActivityCounters[i].readSummaryFromParcelLocked(in);
+ u.mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in);
+ u.mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in);
}
+ u.mMobileRadioActiveTime.readSummaryFromParcelLocked(in);
+ u.mMobileRadioActiveCount.readSummaryFromParcelLocked(in);
}
int NW = in.readInt();
@@ -5678,7 +7102,7 @@ public final class BatteryStatsImpl extends BatteryStats {
p.mSpeedBins = new SamplingCounter[NSB];
for (int i=0; i<NSB; i++) {
if (in.readInt() != 0) {
- p.mSpeedBins[i] = new SamplingCounter(mUnpluggables);
+ p.mSpeedBins[i] = new SamplingCounter(mOnBatteryTimeBase);
p.mSpeedBins[i].readSummaryFromParcelLocked(in);
}
}
@@ -5719,50 +7143,65 @@ public final class BatteryStatsImpl extends BatteryStats {
* @param out the Parcel to be written to.
*/
public void writeSummaryToParcel(Parcel out) {
- // Need to update with current kernel wake lock counts.
- updateKernelWakelocksLocked();
- updateNetworkActivityLocked();
+ pullPendingStateUpdatesLocked();
final long NOW_SYS = SystemClock.uptimeMillis() * 1000;
final long NOWREAL_SYS = SystemClock.elapsedRealtime() * 1000;
- final long NOW = getBatteryUptimeLocked(NOW_SYS);
- final long NOWREAL = getBatteryRealtimeLocked(NOWREAL_SYS);
out.writeInt(VERSION);
writeHistory(out, true);
out.writeInt(mStartCount);
- out.writeLong(computeBatteryUptime(NOW_SYS, STATS_SINCE_CHARGED));
- out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED));
out.writeLong(computeUptime(NOW_SYS, STATS_SINCE_CHARGED));
out.writeLong(computeRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED));
+ out.writeLong(mStartClockTime);
+ mOnBatteryTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS);
+ mOnBatteryScreenOffTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS);
out.writeInt(mDischargeUnplugLevel);
+ out.writeInt(mDischargePlugLevel);
out.writeInt(mDischargeCurrentLevel);
+ out.writeInt(mCurrentBatteryLevel);
out.writeInt(getLowDischargeAmountSinceCharge());
out.writeInt(getHighDischargeAmountSinceCharge());
out.writeInt(getDischargeAmountScreenOnSinceCharge());
out.writeInt(getDischargeAmountScreenOffSinceCharge());
-
- mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ out.writeInt(mNumDischargeStepDurations);
+ out.writeLongArray(mDischargeStepDurations);
+ out.writeInt(mNumChargeStepDurations);
+ out.writeLongArray(mChargeStepDurations);
+
+ mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL);
+ mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
}
mInputEventCounter.writeSummaryFromParcelLocked(out);
- mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
- mPhoneSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL);
+ mPhoneSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
}
- mPhoneSignalScanningTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ mPhoneSignalScanningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
- mPhoneDataConnectionsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL);
+ mPhoneDataConnectionsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
}
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i].writeSummaryFromParcelLocked(out);
+ mNetworkByteActivityCounters[i].writeSummaryFromParcelLocked(out);
+ mNetworkPacketActivityCounters[i].writeSummaryFromParcelLocked(out);
+ }
+ mMobileRadioActiveTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ mMobileRadioActivePerAppTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ mMobileRadioActiveAdjustedTime.writeSummaryFromParcelLocked(out);
+ mMobileRadioActiveUnknownTime.writeSummaryFromParcelLocked(out);
+ mMobileRadioActiveUnknownCount.writeSummaryFromParcelLocked(out);
+ mWifiOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ mGlobalWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ mWifiStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ }
+ mBluetoothOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
+ mBluetoothStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
}
- mWifiOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
- mGlobalWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL);
- mBluetoothOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
out.writeInt(mKernelWakelockStats.size());
for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) {
@@ -5770,7 +7209,19 @@ public final class BatteryStatsImpl extends BatteryStats {
if (kwlt != null) {
out.writeInt(1);
out.writeString(ent.getKey());
- ent.getValue().writeSummaryFromParcelLocked(out, NOWREAL);
+ kwlt.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ } else {
+ out.writeInt(0);
+ }
+ }
+
+ out.writeInt(mWakeupReasonStats.size());
+ for (Map.Entry<String, LongSamplingCounter> ent : mWakeupReasonStats.entrySet()) {
+ LongSamplingCounter counter = ent.getValue();
+ if (counter != null) {
+ out.writeInt(1);
+ out.writeString(ent.getKey());
+ counter.writeSummaryFromParcelLocked(out);
} else {
out.writeInt(0);
}
@@ -5785,57 +7236,57 @@ public final class BatteryStatsImpl extends BatteryStats {
if (u.mWifiRunningTimer != null) {
out.writeInt(1);
- u.mWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (u.mFullWifiLockTimer != null) {
out.writeInt(1);
- u.mFullWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mFullWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (u.mWifiScanTimer != null) {
out.writeInt(1);
- u.mWifiScanTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mWifiScanTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
for (int i = 0; i < Uid.NUM_WIFI_BATCHED_SCAN_BINS; i++) {
if (u.mWifiBatchedScanTimer[i] != null) {
out.writeInt(1);
- u.mWifiBatchedScanTimer[i].writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mWifiBatchedScanTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
}
if (u.mWifiMulticastTimer != null) {
out.writeInt(1);
- u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (u.mAudioTurnedOnTimer != null) {
out.writeInt(1);
- u.mAudioTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mAudioTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (u.mVideoTurnedOnTimer != null) {
out.writeInt(1);
- u.mVideoTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mVideoTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (u.mForegroundActivityTimer != null) {
out.writeInt(1);
- u.mForegroundActivityTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mForegroundActivityTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (u.mVibratorOnTimer != null) {
out.writeInt(1);
- u.mVibratorOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mVibratorOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
@@ -5849,13 +7300,16 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- if (u.mNetworkActivityCounters == null) {
+ if (u.mNetworkByteActivityCounters == null) {
out.writeInt(0);
} else {
out.writeInt(1);
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- u.mNetworkActivityCounters[i].writeSummaryFromParcelLocked(out);
+ u.mNetworkByteActivityCounters[i].writeSummaryFromParcelLocked(out);
+ u.mNetworkPacketActivityCounters[i].writeSummaryFromParcelLocked(out);
}
+ u.mMobileRadioActiveTime.writeSummaryFromParcelLocked(out);
+ u.mMobileRadioActiveCount.writeSummaryFromParcelLocked(out);
}
int NW = u.mWakelockStats.size();
@@ -5867,19 +7321,19 @@ public final class BatteryStatsImpl extends BatteryStats {
Uid.Wakelock wl = ent.getValue();
if (wl.mTimerFull != null) {
out.writeInt(1);
- wl.mTimerFull.writeSummaryFromParcelLocked(out, NOWREAL);
+ wl.mTimerFull.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (wl.mTimerPartial != null) {
out.writeInt(1);
- wl.mTimerPartial.writeSummaryFromParcelLocked(out, NOWREAL);
+ wl.mTimerPartial.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (wl.mTimerWindow != null) {
out.writeInt(1);
- wl.mTimerWindow.writeSummaryFromParcelLocked(out, NOWREAL);
+ wl.mTimerWindow.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
@@ -5895,7 +7349,7 @@ public final class BatteryStatsImpl extends BatteryStats {
Uid.Sensor se = ent.getValue();
if (se.mTimer != null) {
out.writeInt(1);
- se.mTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ se.mTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
@@ -5942,7 +7396,8 @@ public final class BatteryStatsImpl extends BatteryStats {
: ps.mServiceStats.entrySet()) {
out.writeString(sent.getKey());
BatteryStatsImpl.Uid.Pkg.Serv ss = sent.getValue();
- long time = ss.getStartTimeToNowLocked(NOW);
+ long time = ss.getStartTimeToNowLocked(
+ mOnBatteryTimeBase.getUptime(NOW_SYS));
out.writeLong(time);
out.writeInt(ss.mStarts);
out.writeInt(ss.mLaunches);
@@ -5966,64 +7421,75 @@ public final class BatteryStatsImpl extends BatteryStats {
readHistory(in, false);
mStartCount = in.readInt();
- mBatteryUptime = in.readLong();
- mBatteryLastUptime = 0;
- mBatteryRealtime = in.readLong();
- mBatteryLastRealtime = 0;
+ mStartClockTime = in.readLong();
+ mUptime = in.readLong();
+ mUptimeStart = in.readLong();
+ mRealtime = in.readLong();
+ mRealtimeStart = in.readLong();
+ mOnBattery = in.readInt() != 0;
+ mOnBatteryInternal = false; // we are no longer really running.
+ mOnBatteryTimeBase.readFromParcel(in);
+ mOnBatteryScreenOffTimeBase.readFromParcel(in);
+
mScreenOn = false;
- mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables, in);
+ mScreenOnTimer = new StopwatchTimer(null, -1, null, mOnBatteryTimeBase, in);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i,
- null, mUnpluggables, in);
+ mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mOnBatteryTimeBase,
+ in);
}
- mInputEventCounter = new Counter(mUnpluggables, in);
+ mInputEventCounter = new Counter(mOnBatteryTimeBase, in);
mPhoneOn = false;
- mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
+ mPhoneOnTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i,
- null, mUnpluggables, in);
+ null, mOnBatteryTimeBase, in);
}
- mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables, in);
+ mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mOnBatteryTimeBase, in);
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i,
- null, mUnpluggables, in);
+ null, mOnBatteryTimeBase, in);
}
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables, in);
- }
+ mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ }
+ mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+ mMobileRadioActiveTimer = new StopwatchTimer(null, -400, null, mOnBatteryTimeBase, in);
+ mMobileRadioActivePerAppTimer = new StopwatchTimer(null, -401, null, mOnBatteryTimeBase,
+ in);
+ mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase, in);
mWifiOn = false;
- mWifiOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
+ mWifiOnTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in);
mGlobalWifiRunning = false;
- mGlobalWifiRunningTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
+ mGlobalWifiRunningTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in);
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ mWifiStateTimer[i] = new StopwatchTimer(null, -600-i,
+ null, mOnBatteryTimeBase, in);
+ }
mBluetoothOn = false;
- mBluetoothOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
- mUptime = in.readLong();
- mUptimeStart = in.readLong();
- mLastUptime = 0;
- mRealtime = in.readLong();
- mRealtimeStart = in.readLong();
- mLastRealtime = 0;
- mOnBattery = in.readInt() != 0;
- mOnBatteryInternal = false; // we are no longer really running.
- mTrackBatteryPastUptime = in.readLong();
- mTrackBatteryUptimeStart = in.readLong();
- mTrackBatteryPastRealtime = in.readLong();
- mTrackBatteryRealtimeStart = in.readLong();
- mUnpluggedBatteryUptime = in.readLong();
- mUnpluggedBatteryRealtime = in.readLong();
+ mBluetoothOnTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in);
+ for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
+ mBluetoothStateTimer[i] = new StopwatchTimer(null, -500-i,
+ null, mOnBatteryTimeBase, in);
+ }
mDischargeUnplugLevel = in.readInt();
+ mDischargePlugLevel = in.readInt();
mDischargeCurrentLevel = in.readInt();
+ mCurrentBatteryLevel = in.readInt();
mLowDischargeAmountSinceCharge = in.readInt();
mHighDischargeAmountSinceCharge = in.readInt();
mDischargeAmountScreenOn = in.readInt();
mDischargeAmountScreenOnSinceCharge = in.readInt();
mDischargeAmountScreenOff = in.readInt();
mDischargeAmountScreenOffSinceCharge = in.readInt();
+ mNumDischargeStepDurations = in.readInt();
+ in.readLongArray(mDischargeStepDurations);
+ mNumChargeStepDurations = in.readInt();
+ in.readLongArray(mChargeStepDurations);
mLastWriteTime = in.readLong();
- mRadioDataUptime = in.readLong();
- mRadioDataStart = -1;
-
mBluetoothPingCount = in.readInt();
mBluetoothPingStart = -1;
@@ -6032,12 +7498,22 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int ikw = 0; ikw < NKW; ikw++) {
if (in.readInt() != 0) {
String wakelockName = in.readString();
- in.readInt(); // Extra 0/1 written by Timer.writeTimerToParcel
- SamplingTimer kwlt = new SamplingTimer(mUnpluggables, mOnBattery, in);
+ SamplingTimer kwlt = new SamplingTimer(mOnBatteryTimeBase, in);
mKernelWakelockStats.put(wakelockName, kwlt);
}
}
+ mWakeupReasonStats.clear();
+ int NWR = in.readInt();
+ for (int iwr = 0; iwr < NWR; iwr++) {
+ if (in.readInt() != 0) {
+ String reasonName = in.readString();
+ LongSamplingCounter counter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase,
+ in);
+ mWakeupReasonStats.put(reasonName, counter);
+ }
+ }
+
mPartialTimers.clear();
mFullTimers.clear();
mWindowTimers.clear();
@@ -6054,7 +7530,7 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int i = 0; i < numUids; i++) {
int uid = in.readInt();
Uid u = new Uid(uid);
- u.readFromParcelLocked(mUnpluggables, in);
+ u.readFromParcelLocked(mOnBatteryTimeBase, mOnBatteryScreenOffTimeBase, in);
mUidStats.append(uid, u);
}
}
@@ -6070,64 +7546,74 @@ public final class BatteryStatsImpl extends BatteryStats {
@SuppressWarnings("unused")
void writeToParcelLocked(Parcel out, boolean inclUids, int flags) {
// Need to update with current kernel wake lock counts.
- updateKernelWakelocksLocked();
- updateNetworkActivityLocked();
+ pullPendingStateUpdatesLocked();
final long uSecUptime = SystemClock.uptimeMillis() * 1000;
final long uSecRealtime = SystemClock.elapsedRealtime() * 1000;
- final long batteryUptime = getBatteryUptimeLocked(uSecUptime);
- final long batteryRealtime = getBatteryRealtimeLocked(uSecRealtime);
+ final long batteryRealtime = mOnBatteryTimeBase.getRealtime(uSecRealtime);
+ final long batteryScreenOffRealtime = mOnBatteryScreenOffTimeBase.getRealtime(uSecRealtime);
out.writeInt(MAGIC);
writeHistory(out, false);
out.writeInt(mStartCount);
- out.writeLong(mBatteryUptime);
- out.writeLong(mBatteryRealtime);
- mScreenOnTimer.writeToParcel(out, batteryRealtime);
+ out.writeLong(mStartClockTime);
+ out.writeLong(mUptime);
+ out.writeLong(mUptimeStart);
+ out.writeLong(mRealtime);
+ out.writeLong(mRealtimeStart);
+ out.writeInt(mOnBattery ? 1 : 0);
+ mOnBatteryTimeBase.writeToParcel(out, uSecUptime, uSecRealtime);
+ mOnBatteryScreenOffTimeBase.writeToParcel(out, uSecUptime, uSecRealtime);
+
+ mScreenOnTimer.writeToParcel(out, uSecRealtime);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- mScreenBrightnessTimer[i].writeToParcel(out, batteryRealtime);
+ mScreenBrightnessTimer[i].writeToParcel(out, uSecRealtime);
}
mInputEventCounter.writeToParcel(out);
- mPhoneOnTimer.writeToParcel(out, batteryRealtime);
+ mPhoneOnTimer.writeToParcel(out, uSecRealtime);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
- mPhoneSignalStrengthsTimer[i].writeToParcel(out, batteryRealtime);
+ mPhoneSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime);
}
- mPhoneSignalScanningTimer.writeToParcel(out, batteryRealtime);
+ mPhoneSignalScanningTimer.writeToParcel(out, uSecRealtime);
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
- mPhoneDataConnectionsTimer[i].writeToParcel(out, batteryRealtime);
+ mPhoneDataConnectionsTimer[i].writeToParcel(out, uSecRealtime);
}
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i].writeToParcel(out);
+ mNetworkByteActivityCounters[i].writeToParcel(out);
+ mNetworkPacketActivityCounters[i].writeToParcel(out);
+ }
+ mMobileRadioActiveTimer.writeToParcel(out, uSecRealtime);
+ mMobileRadioActivePerAppTimer.writeToParcel(out, uSecRealtime);
+ mMobileRadioActiveAdjustedTime.writeToParcel(out);
+ mMobileRadioActiveUnknownTime.writeToParcel(out);
+ mMobileRadioActiveUnknownCount.writeToParcel(out);
+ mWifiOnTimer.writeToParcel(out, uSecRealtime);
+ mGlobalWifiRunningTimer.writeToParcel(out, uSecRealtime);
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ mWifiStateTimer[i].writeToParcel(out, uSecRealtime);
+ }
+ mBluetoothOnTimer.writeToParcel(out, uSecRealtime);
+ for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
+ mBluetoothStateTimer[i].writeToParcel(out, uSecRealtime);
}
- mWifiOnTimer.writeToParcel(out, batteryRealtime);
- mGlobalWifiRunningTimer.writeToParcel(out, batteryRealtime);
- mBluetoothOnTimer.writeToParcel(out, batteryRealtime);
- out.writeLong(mUptime);
- out.writeLong(mUptimeStart);
- out.writeLong(mRealtime);
- out.writeLong(mRealtimeStart);
- out.writeInt(mOnBattery ? 1 : 0);
- out.writeLong(batteryUptime);
- out.writeLong(mTrackBatteryUptimeStart);
- out.writeLong(batteryRealtime);
- out.writeLong(mTrackBatteryRealtimeStart);
- out.writeLong(mUnpluggedBatteryUptime);
- out.writeLong(mUnpluggedBatteryRealtime);
out.writeInt(mDischargeUnplugLevel);
+ out.writeInt(mDischargePlugLevel);
out.writeInt(mDischargeCurrentLevel);
+ out.writeInt(mCurrentBatteryLevel);
out.writeInt(mLowDischargeAmountSinceCharge);
out.writeInt(mHighDischargeAmountSinceCharge);
out.writeInt(mDischargeAmountScreenOn);
out.writeInt(mDischargeAmountScreenOnSinceCharge);
out.writeInt(mDischargeAmountScreenOff);
out.writeInt(mDischargeAmountScreenOffSinceCharge);
+ out.writeInt(mNumDischargeStepDurations);
+ out.writeLongArray(mDischargeStepDurations);
+ out.writeInt(mNumChargeStepDurations);
+ out.writeLongArray(mChargeStepDurations);
out.writeLong(mLastWriteTime);
- // Write radio uptime for data
- out.writeLong(getRadioDataUptime());
-
out.writeInt(getBluetoothPingCount());
if (inclUids) {
@@ -6137,7 +7623,18 @@ public final class BatteryStatsImpl extends BatteryStats {
if (kwlt != null) {
out.writeInt(1);
out.writeString(ent.getKey());
- Timer.writeTimerToParcel(out, kwlt, batteryRealtime);
+ kwlt.writeToParcel(out, uSecRealtime);
+ } else {
+ out.writeInt(0);
+ }
+ }
+ out.writeInt(mWakeupReasonStats.size());
+ for (Map.Entry<String, LongSamplingCounter> ent : mWakeupReasonStats.entrySet()) {
+ LongSamplingCounter counter = ent.getValue();
+ if (counter != null) {
+ out.writeInt(1);
+ out.writeString(ent.getKey());
+ counter.writeToParcel(out);
} else {
out.writeInt(0);
}
@@ -6155,7 +7652,7 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(mUidStats.keyAt(i));
Uid uid = mUidStats.valueAt(i);
- uid.writeToParcelLocked(out, batteryRealtime);
+ uid.writeToParcelLocked(out, uSecRealtime);
}
} else {
out.writeInt(0);
@@ -6175,12 +7672,15 @@ public final class BatteryStatsImpl extends BatteryStats {
public void prepareForDumpLocked() {
// Need to retrieve current kernel wake lock stats before printing.
- updateKernelWakelocksLocked();
- updateNetworkActivityLocked();
+ pullPendingStateUpdatesLocked();
}
- public void dumpLocked(PrintWriter pw, boolean isUnpluggedOnly, int reqUid) {
+ public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) {
if (DEBUG) {
+ pw.println("mOnBatteryTimeBase:");
+ mOnBatteryTimeBase.dump(pw, " ");
+ pw.println("mOnBatteryScreenOffTimeBase:");
+ mOnBatteryScreenOffTimeBase.dump(pw, " ");
Printer pr = new PrintWriterPrinter(pw);
pr.println("*** Screen timer:");
mScreenOnTimer.logState(pr, " ");
@@ -6202,13 +7702,26 @@ public final class BatteryStatsImpl extends BatteryStats {
pr.println("*** Data connection type #" + i + ":");
mPhoneDataConnectionsTimer[i].logState(pr, " ");
}
+ pr.println("*** mMobileRadioPowerState=" + mMobileRadioPowerState);
+ pr.println("*** Mobile network active timer:");
+ mMobileRadioActiveTimer.logState(pr, " ");
+ pr.println("*** Mobile network active adjusted timer:");
+ mMobileRadioActiveAdjustedTime.logState(pr, " ");
pr.println("*** Wifi timer:");
mWifiOnTimer.logState(pr, " ");
pr.println("*** WifiRunning timer:");
mGlobalWifiRunningTimer.logState(pr, " ");
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ pr.println("*** Wifi state #" + i + ":");
+ mWifiStateTimer[i].logState(pr, " ");
+ }
pr.println("*** Bluetooth timer:");
mBluetoothOnTimer.logState(pr, " ");
+ for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
+ pr.println("*** Bluetooth active type #" + i + ":");
+ mBluetoothStateTimer[i].logState(pr, " ");
+ }
}
- super.dumpLocked(pw, isUnpluggedOnly, reqUid);
+ super.dumpLocked(context, pw, flags, reqUid, histStart);
}
}
diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java
index d9e3ef6..40834ba 100644
--- a/core/java/com/android/internal/os/HandlerCaller.java
+++ b/core/java/com/android/internal/os/HandlerCaller.java
@@ -85,7 +85,27 @@ public class HandlerCaller {
public void sendMessage(Message msg) {
mH.sendMessage(msg);
}
-
+
+ public SomeArgs sendMessageAndWait(Message msg) {
+ if (Looper.myLooper() == mH.getLooper()) {
+ throw new IllegalStateException("Can't wait on same thread as looper");
+ }
+ SomeArgs args = (SomeArgs)msg.obj;
+ args.mWaitState = SomeArgs.WAIT_WAITING;
+ mH.sendMessage(msg);
+ synchronized (args) {
+ while (args.mWaitState == SomeArgs.WAIT_WAITING) {
+ try {
+ args.wait();
+ } catch (InterruptedException e) {
+ return null;
+ }
+ }
+ }
+ args.mWaitState = SomeArgs.WAIT_NONE;
+ return args;
+ }
+
public Message obtainMessage(int what) {
return mH.obtainMessage(what);
}
@@ -136,6 +156,14 @@ public class HandlerCaller {
return mH.obtainMessage(what, arg1, 0, args);
}
+ public Message obtainMessageIOOO(int what, int arg1, Object arg2, Object arg3, Object arg4) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg2;
+ args.arg2 = arg3;
+ args.arg3 = arg4;
+ return mH.obtainMessage(what, arg1, 0, args);
+ }
+
public Message obtainMessageOO(int what, Object arg1, Object arg2) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = arg1;
@@ -161,6 +189,17 @@ public class HandlerCaller {
return mH.obtainMessage(what, 0, 0, args);
}
+ public Message obtainMessageOOOOO(int what, Object arg1, Object arg2,
+ Object arg3, Object arg4, Object arg5) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg1;
+ args.arg2 = arg2;
+ args.arg3 = arg3;
+ args.arg4 = arg4;
+ args.arg5 = arg5;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+
public Message obtainMessageIIII(int what, int arg1, int arg2,
int arg3, int arg4) {
SomeArgs args = SomeArgs.obtain();
diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java
index 6fb72f1..7edf4cc 100644
--- a/core/java/com/android/internal/os/SomeArgs.java
+++ b/core/java/com/android/internal/os/SomeArgs.java
@@ -35,6 +35,11 @@ public final class SomeArgs {
private boolean mInPool;
+ static final int WAIT_NONE = 0;
+ static final int WAIT_WAITING = 1;
+ static final int WAIT_FINISHED = 2;
+ int mWaitState = WAIT_NONE;
+
public Object arg1;
public Object arg2;
public Object arg3;
@@ -70,6 +75,9 @@ public final class SomeArgs {
if (mInPool) {
throw new IllegalStateException("Already recycled.");
}
+ if (mWaitState != WAIT_NONE) {
+ return;
+ }
synchronized (sPoolLock) {
clear();
if (sPoolSize < MAX_POOL_SIZE) {
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index 63ff5a0..b78c70f 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -25,12 +25,24 @@ import android.os.Bundle;
interface IKeyguardService {
boolean isShowing();
boolean isSecure();
- boolean isShowingAndNotHidden();
+ boolean isShowingAndNotOccluded();
boolean isInputRestricted();
boolean isDismissable();
oneway void verifyUnlock(IKeyguardExitCallback callback);
oneway void keyguardDone(boolean authenticated, boolean wakeup);
- oneway void setHidden(boolean isHidden);
+
+ /**
+ * Sets the Keyguard as occluded when a window dismisses the Keyguard with flag
+ * FLAG_SHOW_ON_LOCK_SCREEN.
+ *
+ * @param isOccluded Whether the Keyguard is occluded by another window.
+ * @return See IKeyguardServiceConstants.KEYGUARD_SERVICE_SET_OCCLUDED_*. This is needed because
+ * PhoneWindowManager needs to set these flags immediately and can't wait for the
+ * Keyguard thread to pick it up. In the hidden case, PhoneWindowManager is solely
+ * responsible to make sure that the flags are unset.
+ */
+ int setOccluded(boolean isOccluded);
+
oneway void dismiss();
oneway void onDreamingStarted();
oneway void onDreamingStopped();
diff --git a/core/java/com/android/internal/policy/IKeyguardServiceConstants.java b/core/java/com/android/internal/policy/IKeyguardServiceConstants.java
new file mode 100644
index 0000000..b88174a
--- /dev/null
+++ b/core/java/com/android/internal/policy/IKeyguardServiceConstants.java
@@ -0,0 +1,41 @@
+/*
+ * 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.internal.policy;
+
+/**
+ * @hide
+ */
+public class IKeyguardServiceConstants {
+
+ /**
+ * Constant for {@link com.android.internal.policy.IKeyguardService#setHidden(boolean)}:
+ * Don't change the keyguard window flags.
+ */
+ public static final int KEYGUARD_SERVICE_SET_OCCLUDED_RESULT_NONE = 0;
+
+ /**
+ * Constant for {@link com.android.internal.policy.IKeyguardService#setHidden(boolean)}:
+ * Set the keyguard window flags to FLAG_SHOW_WALLPAPER and PRIVATE_FLAG_KEYGUARD.
+ */
+ public static final int KEYGUARD_SERVICE_SET_OCCLUDED_RESULT_SET_FLAGS = 1;
+
+ /**
+ * Constant for {@link com.android.internal.policy.IKeyguardService#setHidden(boolean)}:
+ * Unset the keyguard window flags to FLAG_SHOW_WALLPAPER and PRIVATE_FLAG_KEYGUARD.
+ */
+ public static final int KEYGUARD_SERVICE_SET_OCCLUDED_RESULT_UNSET_FLAGS = 2;
+}
diff --git a/core/java/com/android/internal/preference/YesNoPreference.java b/core/java/com/android/internal/preference/YesNoPreference.java
index cf68a58..7abf416 100644
--- a/core/java/com/android/internal/preference/YesNoPreference.java
+++ b/core/java/com/android/internal/preference/YesNoPreference.java
@@ -31,15 +31,19 @@ import android.util.AttributeSet;
*/
public class YesNoPreference extends DialogPreference {
private boolean mWasPositiveResult;
-
- public YesNoPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+
+ public YesNoPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public YesNoPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
public YesNoPreference(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.yesNoPreferenceStyle);
}
-
+
public YesNoPreference(Context context) {
this(context, null);
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 97ea7d8..6428e15 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -41,11 +41,14 @@ interface IStatusBarService
out List<IBinder> notificationKeys, out List<StatusBarNotification> notifications,
out int[] switches, out List<IBinder> binders);
void onPanelRevealed();
- void onNotificationClick(String pkg, String tag, int id);
+ void onPanelHidden();
+ void onNotificationClick(String pkg, String tag, int id, int userId);
void onNotificationError(String pkg, String tag, int id,
- int uid, int initialPid, String message);
- void onClearAllNotifications();
- void onNotificationClear(String pkg, String tag, int id);
+ int uid, int initialPid, String message, int userId);
+ void onClearAllNotifications(int userId);
+ void onNotificationClear(String pkg, String tag, int id, int userId);
+ void onNotificationVisibilityChanged(
+ in String[] newlyVisibleKeys, in String[] noLongerVisibleKeys);
void setSystemUiVisibility(int vis, int mask);
void setHardKeyboardEnabled(boolean enabled);
void toggleRecentApps();
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 9137d3c..d177410 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -16,11 +16,10 @@
package com.android.internal.util;
-import java.lang.reflect.Array;
+import dalvik.system.VMRuntime;
+import libcore.util.EmptyArray;
-// XXX these should be changed to reflect the actual memory allocator we use.
-// it looks like right now objects want to be powers of 2 minus 8
-// and the array size eats another 4 bytes
+import java.lang.reflect.Array;
/**
* ArrayUtils contains some methods that you can call to find out
@@ -28,46 +27,42 @@ import java.lang.reflect.Array;
*/
public class ArrayUtils
{
- private static Object[] EMPTY = new Object[0];
private static final int CACHE_SIZE = 73;
private static Object[] sCache = new Object[CACHE_SIZE];
private ArrayUtils() { /* cannot be instantiated */ }
- public static int idealByteArraySize(int need) {
- for (int i = 4; i < 32; i++)
- if (need <= (1 << i) - 12)
- return (1 << i) - 12;
-
- return need;
+ public static byte[] newUnpaddedByteArray(int minLen) {
+ return (byte[])VMRuntime.getRuntime().newUnpaddedArray(byte.class, minLen);
}
- public static int idealBooleanArraySize(int need) {
- return idealByteArraySize(need);
+ public static char[] newUnpaddedCharArray(int minLen) {
+ return (char[])VMRuntime.getRuntime().newUnpaddedArray(char.class, minLen);
}
- public static int idealShortArraySize(int need) {
- return idealByteArraySize(need * 2) / 2;
+ public static int[] newUnpaddedIntArray(int minLen) {
+ return (int[])VMRuntime.getRuntime().newUnpaddedArray(int.class, minLen);
}
- public static int idealCharArraySize(int need) {
- return idealByteArraySize(need * 2) / 2;
+ public static boolean[] newUnpaddedBooleanArray(int minLen) {
+ return (boolean[])VMRuntime.getRuntime().newUnpaddedArray(boolean.class, minLen);
}
- public static int idealIntArraySize(int need) {
- return idealByteArraySize(need * 4) / 4;
+ public static long[] newUnpaddedLongArray(int minLen) {
+ return (long[])VMRuntime.getRuntime().newUnpaddedArray(long.class, minLen);
}
- public static int idealFloatArraySize(int need) {
- return idealByteArraySize(need * 4) / 4;
+ public static float[] newUnpaddedFloatArray(int minLen) {
+ return (float[])VMRuntime.getRuntime().newUnpaddedArray(float.class, minLen);
}
- public static int idealObjectArraySize(int need) {
- return idealByteArraySize(need * 4) / 4;
+ public static Object[] newUnpaddedObjectArray(int minLen) {
+ return (Object[])VMRuntime.getRuntime().newUnpaddedArray(Object.class, minLen);
}
- public static int idealLongArraySize(int need) {
- return idealByteArraySize(need * 8) / 8;
+ @SuppressWarnings("unchecked")
+ public static <T> T[] newUnpaddedArray(Class<T> clazz, int minLen) {
+ return (T[])VMRuntime.getRuntime().newUnpaddedArray(clazz, minLen);
}
/**
@@ -102,9 +97,10 @@ public class ArrayUtils
* it will return the same empty array every time to avoid reallocation,
* although this is not guaranteed.
*/
+ @SuppressWarnings("unchecked")
public static <T> T[] emptyArray(Class<T> kind) {
if (kind == Object.class) {
- return (T[]) EMPTY;
+ return (T[]) EmptyArray.OBJECT;
}
int bucket = ((System.identityHashCode(kind) / 8) & 0x7FFFFFFF) % CACHE_SIZE;
diff --git a/core/java/com/android/internal/util/GrowingArrayUtils.java b/core/java/com/android/internal/util/GrowingArrayUtils.java
new file mode 100644
index 0000000..b4d2d730
--- /dev/null
+++ b/core/java/com/android/internal/util/GrowingArrayUtils.java
@@ -0,0 +1,196 @@
+/*
+ * 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.internal.util;
+
+/**
+ * A helper class that aims to provide comparable growth performance to ArrayList, but on primitive
+ * arrays. Common array operations are implemented for efficient use in dynamic containers.
+ *
+ * All methods in this class assume that the length of an array is equivalent to its capacity and
+ * NOT the number of elements in the array. The current size of the array is always passed in as a
+ * parameter.
+ *
+ * @hide
+ */
+public final class GrowingArrayUtils {
+
+ /**
+ * Appends an element to the end of the array, growing the array if there is no more room.
+ * @param array The array to which to append the element. This must NOT be null.
+ * @param currentSize The number of elements in the array. Must be less than or equal to
+ * array.length.
+ * @param element The element to append.
+ * @return the array to which the element was appended. This may be different than the given
+ * array.
+ */
+ public static <T> T[] append(T[] array, int currentSize, T element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 > array.length) {
+ @SuppressWarnings("unchecked")
+ T[] newArray = ArrayUtils.newUnpaddedArray(
+ (Class<T>) array.getClass().getComponentType(), growSize(currentSize));
+ System.arraycopy(array, 0, newArray, 0, currentSize);
+ array = newArray;
+ }
+ array[currentSize] = element;
+ return array;
+ }
+
+ /**
+ * Primitive int version of {@link #append(Object[], int, Object)}.
+ */
+ public static int[] append(int[] array, int currentSize, int element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 > array.length) {
+ int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
+ System.arraycopy(array, 0, newArray, 0, currentSize);
+ array = newArray;
+ }
+ array[currentSize] = element;
+ return array;
+ }
+
+ /**
+ * Primitive long version of {@link #append(Object[], int, Object)}.
+ */
+ public static long[] append(long[] array, int currentSize, long element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 > array.length) {
+ long[] newArray = ArrayUtils.newUnpaddedLongArray(growSize(currentSize));
+ System.arraycopy(array, 0, newArray, 0, currentSize);
+ array = newArray;
+ }
+ array[currentSize] = element;
+ return array;
+ }
+
+ /**
+ * Primitive boolean version of {@link #append(Object[], int, Object)}.
+ */
+ public static boolean[] append(boolean[] array, int currentSize, boolean element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 > array.length) {
+ boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(growSize(currentSize));
+ System.arraycopy(array, 0, newArray, 0, currentSize);
+ array = newArray;
+ }
+ array[currentSize] = element;
+ return array;
+ }
+
+ /**
+ * Inserts an element into the array at the specified index, growing the array if there is no
+ * more room.
+ *
+ * @param array The array to which to append the element. Must NOT be null.
+ * @param currentSize The number of elements in the array. Must be less than or equal to
+ * array.length.
+ * @param element The element to insert.
+ * @return the array to which the element was appended. This may be different than the given
+ * array.
+ */
+ public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 <= array.length) {
+ System.arraycopy(array, index, array, index + 1, currentSize - index);
+ array[index] = element;
+ return array;
+ }
+
+ @SuppressWarnings("unchecked")
+ T[] newArray = ArrayUtils.newUnpaddedArray((Class<T>)array.getClass().getComponentType(),
+ growSize(currentSize));
+ System.arraycopy(array, 0, newArray, 0, index);
+ newArray[index] = element;
+ System.arraycopy(array, index, newArray, index + 1, array.length - index);
+ return newArray;
+ }
+
+ /**
+ * Primitive int version of {@link #insert(Object[], int, int, Object)}.
+ */
+ public static int[] insert(int[] array, int currentSize, int index, int element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 <= array.length) {
+ System.arraycopy(array, index, array, index + 1, currentSize - index);
+ array[index] = element;
+ return array;
+ }
+
+ int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
+ System.arraycopy(array, 0, newArray, 0, index);
+ newArray[index] = element;
+ System.arraycopy(array, index, newArray, index + 1, array.length - index);
+ return newArray;
+ }
+
+ /**
+ * Primitive long version of {@link #insert(Object[], int, int, Object)}.
+ */
+ public static long[] insert(long[] array, int currentSize, int index, long element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 <= array.length) {
+ System.arraycopy(array, index, array, index + 1, currentSize - index);
+ array[index] = element;
+ return array;
+ }
+
+ long[] newArray = ArrayUtils.newUnpaddedLongArray(growSize(currentSize));
+ System.arraycopy(array, 0, newArray, 0, index);
+ newArray[index] = element;
+ System.arraycopy(array, index, newArray, index + 1, array.length - index);
+ return newArray;
+ }
+
+ /**
+ * Primitive boolean version of {@link #insert(Object[], int, int, Object)}.
+ */
+ public static boolean[] insert(boolean[] array, int currentSize, int index, boolean element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 <= array.length) {
+ System.arraycopy(array, index, array, index + 1, currentSize - index);
+ array[index] = element;
+ return array;
+ }
+
+ boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(growSize(currentSize));
+ System.arraycopy(array, 0, newArray, 0, index);
+ newArray[index] = element;
+ System.arraycopy(array, index, newArray, index + 1, array.length - index);
+ return newArray;
+ }
+
+ /**
+ * Given the current size of an array, returns an ideal size to which the array should grow.
+ * This is typically double the given size, but should not be relied upon to do so in the
+ * future.
+ */
+ public static int growSize(int currentSize) {
+ return currentSize <= 4 ? 8 : currentSize * 2;
+ }
+
+ // Uninstantiable
+ private GrowingArrayUtils() {}
+}
diff --git a/core/java/com/android/internal/util/ImageUtils.java b/core/java/com/android/internal/util/ImageUtils.java
new file mode 100644
index 0000000..a5ce6e0
--- /dev/null
+++ b/core/java/com/android/internal/util/ImageUtils.java
@@ -0,0 +1,84 @@
+/*
+ * 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.internal.util;
+
+import android.graphics.Bitmap;
+
+/**
+ * Utility class for image analysis and processing.
+ *
+ * @hide
+ */
+public class ImageUtils {
+
+ // Amount (max is 255) that two channels can differ before the color is no longer "gray".
+ private static final int TOLERANCE = 20;
+
+ // Alpha amount for which values below are considered transparent.
+ private static final int ALPHA_TOLERANCE = 50;
+
+ private int[] mTempBuffer;
+
+ /**
+ * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect
+ * gray".
+ */
+ public boolean isGrayscale(Bitmap bitmap) {
+ final int height = bitmap.getHeight();
+ final int width = bitmap.getWidth();
+ int size = height*width;
+
+ ensureBufferSize(size);
+ bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height);
+ for (int i = 0; i < size; i++) {
+ if (!isGrayscale(mTempBuffer[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Makes sure that {@code mTempBuffer} has at least length {@code size}.
+ */
+ private void ensureBufferSize(int size) {
+ if (mTempBuffer == null || mTempBuffer.length < size) {
+ mTempBuffer = new int[size];
+ }
+ }
+
+ /**
+ * Classifies a color as grayscale or not. Grayscale here means "very close to a perfect
+ * gray"; if all three channels are approximately equal, this will return true.
+ *
+ * Note that really transparent colors are always grayscale.
+ */
+ public static boolean isGrayscale(int color) {
+ int alpha = 0xFF & (color >> 24);
+ if (alpha < ALPHA_TOLERANCE) {
+ return true;
+ }
+
+ int r = 0xFF & (color >> 16);
+ int g = 0xFF & (color >> 8);
+ int b = 0xFF & color;
+
+ return Math.abs(r - g) < TOLERANCE
+ && Math.abs(r - b) < TOLERANCE
+ && Math.abs(g - b) < TOLERANCE;
+ }
+}
diff --git a/core/java/com/android/internal/util/LegacyNotificationUtil.java b/core/java/com/android/internal/util/LegacyNotificationUtil.java
new file mode 100644
index 0000000..0394bbc
--- /dev/null
+++ b/core/java/com/android/internal/util/LegacyNotificationUtil.java
@@ -0,0 +1,193 @@
+/*
+ * 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.internal.util;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.TextAppearanceSpan;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.Arrays;
+import java.util.WeakHashMap;
+
+/**
+ * Helper class to process legacy (Holo) notifications to make them look like quantum notifications.
+ *
+ * @hide
+ */
+public class LegacyNotificationUtil {
+
+ private static final String TAG = "LegacyNotificationUtil";
+
+ private static final Object sLock = new Object();
+ private static LegacyNotificationUtil sInstance;
+
+ private final ImageUtils mImageUtils = new ImageUtils();
+ private final WeakHashMap<Bitmap, Pair<Boolean, Integer>> mGrayscaleBitmapCache =
+ new WeakHashMap<Bitmap, Pair<Boolean, Integer>>();
+
+ public static LegacyNotificationUtil getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new LegacyNotificationUtil();
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect
+ * gray".
+ *
+ * @param bitmap The bitmap to test.
+ * @return Whether the bitmap is grayscale.
+ */
+ public boolean isGrayscale(Bitmap bitmap) {
+ synchronized (sLock) {
+ Pair<Boolean, Integer> cached = mGrayscaleBitmapCache.get(bitmap);
+ if (cached != null) {
+ if (cached.second == bitmap.getGenerationId()) {
+ return cached.first;
+ }
+ }
+ }
+ boolean result;
+ int generationId;
+ synchronized (mImageUtils) {
+ result = mImageUtils.isGrayscale(bitmap);
+
+ // generationId and the check whether the Bitmap is grayscale can't be read atomically
+ // here. However, since the thread is in the process of posting the notification, we can
+ // assume that it doesn't modify the bitmap while we are checking the pixels.
+ generationId = bitmap.getGenerationId();
+ }
+ synchronized (sLock) {
+ mGrayscaleBitmapCache.put(bitmap, Pair.create(result, generationId));
+ }
+ return result;
+ }
+
+ /**
+ * Checks whether a drawable is grayscale. Grayscale here means "very close to a perfect
+ * gray".
+ *
+ * @param d The drawable to test.
+ * @return Whether the drawable is grayscale.
+ */
+ public boolean isGrayscale(Drawable d) {
+ if (d == null) {
+ return false;
+ } else if (d instanceof BitmapDrawable) {
+ BitmapDrawable bd = (BitmapDrawable) d;
+ return bd.getBitmap() != null && isGrayscale(bd.getBitmap());
+ } else if (d instanceof AnimationDrawable) {
+ AnimationDrawable ad = (AnimationDrawable) d;
+ int count = ad.getNumberOfFrames();
+ return count > 0 && isGrayscale(ad.getFrame(0));
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks whether a drawable with a resoure id is grayscale. Grayscale here means "very close
+ * to a perfect gray".
+ *
+ * @param context The context to load the drawable from.
+ * @return Whether the drawable is grayscale.
+ */
+ public boolean isGrayscale(Context context, int drawableResId) {
+ if (drawableResId != 0) {
+ try {
+ return isGrayscale(context.getDrawable(drawableResId));
+ } catch (Resources.NotFoundException ex) {
+ Log.e(TAG, "Drawable not found: " + drawableResId);
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Inverts all the grayscale colors set by {@link android.text.style.TextAppearanceSpan}s on
+ * the text.
+ *
+ * @param charSequence The text to process.
+ * @return The color inverted text.
+ */
+ public CharSequence invertCharSequenceColors(CharSequence charSequence) {
+ if (charSequence instanceof Spanned) {
+ Spanned ss = (Spanned) charSequence;
+ Object[] spans = ss.getSpans(0, ss.length(), Object.class);
+ SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
+ for (Object span : spans) {
+ Object resultSpan = span;
+ if (span instanceof TextAppearanceSpan) {
+ resultSpan = processTextAppearanceSpan((TextAppearanceSpan) span);
+ }
+ builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
+ ss.getSpanFlags(span));
+ }
+ return builder;
+ }
+ return charSequence;
+ }
+
+ private TextAppearanceSpan processTextAppearanceSpan(TextAppearanceSpan span) {
+ ColorStateList colorStateList = span.getTextColor();
+ if (colorStateList != null) {
+ int[] colors = colorStateList.getColors();
+ boolean changed = false;
+ for (int i = 0; i < colors.length; i++) {
+ if (ImageUtils.isGrayscale(colors[i])) {
+
+ // Allocate a new array so we don't change the colors in the old color state
+ // list.
+ if (!changed) {
+ colors = Arrays.copyOf(colors, colors.length);
+ }
+ colors[i] = processColor(colors[i]);
+ changed = true;
+ }
+ }
+ if (changed) {
+ return new TextAppearanceSpan(
+ span.getFamily(), span.getTextStyle(), span.getTextSize(),
+ new ColorStateList(colorStateList.getStates(), colors),
+ span.getLinkTextColor());
+ }
+ }
+ return span;
+ }
+
+ private int processColor(int color) {
+ return Color.argb(Color.alpha(color),
+ 255 - Color.red(color),
+ 255 - Color.green(color),
+ 255 - Color.blue(color));
+ }
+}
diff --git a/core/java/com/android/internal/view/ActionBarPolicy.java b/core/java/com/android/internal/view/ActionBarPolicy.java
index 25086c5..bee59dc 100644
--- a/core/java/com/android/internal/view/ActionBarPolicy.java
+++ b/core/java/com/android/internal/view/ActionBarPolicy.java
@@ -19,11 +19,9 @@ package com.android.internal.view;
import com.android.internal.R;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
-import android.view.ViewConfiguration;
/**
* Allows components to query for various configuration policy decisions
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
index ce4312d..9e8d12b 100644
--- a/core/java/com/android/internal/view/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -27,4 +27,5 @@ oneway interface IInputMethodClient {
void onBindMethod(in InputBindResult res);
void onUnbindMethod(int sequence);
void setActive(boolean active);
+ void setCursorAnchorMonitorMode(int monitorMode);
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 3724a08..5336174 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -32,7 +32,9 @@ import com.android.internal.view.IInputMethodClient;
* this file.
*/
interface IInputMethodManager {
+ // TODO: Use ParceledListSlice instead
List<InputMethodInfo> getInputMethodList();
+ // TODO: Use ParceledListSlice instead
List<InputMethodInfo> getEnabledInputMethodList();
List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId,
boolean allowsImplicitlySelectedSubtypes);
@@ -74,4 +76,7 @@ interface IInputMethodManager {
boolean shouldOfferSwitchingToNextInputMethod(in IBinder token);
boolean setInputMethodEnabled(String id, boolean enabled);
void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
+ int getInputMethodWindowVisibleHeight();
+ oneway void notifyTextCommitted();
+ void setCursorAnchorMonitorMode(in IBinder token, int monitorMode);
}
diff --git a/core/java/com/android/internal/view/RotationPolicy.java b/core/java/com/android/internal/view/RotationPolicy.java
index 6295314..b479cb1 100644
--- a/core/java/com/android/internal/view/RotationPolicy.java
+++ b/core/java/com/android/internal/view/RotationPolicy.java
@@ -18,7 +18,9 @@ package com.android.internal.view;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.database.ContentObserver;
+import android.graphics.Point;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
@@ -26,15 +28,20 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
+import android.view.Display;
import android.view.IWindowManager;
import android.view.Surface;
import android.view.WindowManagerGlobal;
+import com.android.internal.R;
+
/**
* Provides helper functions for configuring the display rotation policy.
*/
public final class RotationPolicy {
private static final String TAG = "RotationPolicy";
+ private static final int CURRENT_ROTATION = -1;
+ private static final int NATURAL_ROTATION = Surface.ROTATION_0;
private RotationPolicy() {
}
@@ -57,23 +64,33 @@ public final class RotationPolicy {
}
/**
- * Returns true if the device supports the rotation-lock toggle feature
- * in the system UI or system bar.
+ * Returns the orientation that will be used when locking the orientation from system UI
+ * with {@link #setRotationLock}.
*
- * When the rotation-lock toggle is supported, the "auto-rotate screen" option in
- * Display settings should be hidden, but it should remain available in Accessibility
- * settings.
+ * If the device only supports locking to its natural orientation, this will be either
+ * Configuration.ORIENTATION_PORTRAIT or Configuration.ORIENTATION_LANDSCAPE,
+ * otherwise Configuration.ORIENTATION_UNDEFINED if any orientation is lockable.
*/
- public static boolean isRotationLockToggleSupported(Context context) {
- return isRotationSupported(context)
- && context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
+ public static int getRotationLockOrientation(Context context) {
+ if (!areAllRotationsAllowed(context)) {
+ final Point size = new Point();
+ final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ try {
+ wm.getInitialDisplaySize(Display.DEFAULT_DISPLAY, size);
+ return size.x < size.y ?
+ Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to get the display size");
+ }
+ }
+ return Configuration.ORIENTATION_UNDEFINED;
}
/**
- * Returns true if the rotation-lock toggle should be shown in the UI.
+ * Returns true if the rotation-lock toggle should be shown in system UI.
*/
public static boolean isRotationLockToggleVisible(Context context) {
- return isRotationLockToggleSupported(context) &&
+ return isRotationSupported(context) &&
Settings.System.getIntForUser(context.getContentResolver(),
Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0,
UserHandle.USER_CURRENT) == 0;
@@ -88,50 +105,42 @@ public final class RotationPolicy {
}
/**
- * Enables or disables rotation lock.
- *
- * Should be used by the rotation lock toggle.
+ * Enables or disables rotation lock from the system UI toggle.
*/
public static void setRotationLock(Context context, final boolean enabled) {
Settings.System.putIntForUser(context.getContentResolver(),
Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0,
UserHandle.USER_CURRENT);
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- try {
- IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- if (enabled) {
- wm.freezeRotation(-1);
- } else {
- wm.thawRotation();
- }
- } catch (RemoteException exc) {
- Log.w(TAG, "Unable to save auto-rotate setting");
- }
- }
- });
+ final int rotation = areAllRotationsAllowed(context) ? CURRENT_ROTATION : NATURAL_ROTATION;
+ setRotationLock(enabled, rotation);
}
/**
- * Enables or disables rotation lock and adjusts whether the rotation lock toggle
- * should be hidden for accessibility purposes.
+ * Enables or disables natural rotation lock from Accessibility settings.
*
- * Should be used by Display settings and Accessibility settings.
+ * If rotation is locked for accessibility, the system UI toggle is hidden to avoid confusion.
*/
public static void setRotationLockForAccessibility(Context context, final boolean enabled) {
Settings.System.putIntForUser(context.getContentResolver(),
Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, enabled ? 1 : 0,
UserHandle.USER_CURRENT);
+ setRotationLock(enabled, NATURAL_ROTATION);
+ }
+
+ private static boolean areAllRotationsAllowed(Context context) {
+ return context.getResources().getBoolean(R.bool.config_allowAllRotations);
+ }
+
+ private static void setRotationLock(final boolean enabled, final int rotation) {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
try {
IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
if (enabled) {
- wm.freezeRotation(Surface.ROTATION_0);
+ wm.freezeRotation(rotation);
} else {
wm.thawRotation();
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java
index 7ca6c1b..ed676bb 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItem.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java
@@ -163,7 +163,7 @@ public class ActionMenuItem implements MenuItem {
public MenuItem setIcon(int iconRes) {
mIconResId = iconRes;
- mIconDrawable = mContext.getResources().getDrawable(iconRes);
+ mIconDrawable = mContext.getDrawable(iconRes);
return this;
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index 238a9c0..891baea 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -28,8 +28,11 @@ import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.ActionMenuView;
+import android.widget.ListPopupWindow;
import android.widget.TextView;
import android.widget.Toast;
+import android.widget.ListPopupWindow.ForwardingListener;
/**
* @hide
@@ -43,6 +46,8 @@ public class ActionMenuItemView extends TextView
private CharSequence mTitle;
private Drawable mIcon;
private MenuBuilder.ItemInvoker mItemInvoker;
+ private ForwardingListener mForwardingListener;
+ private PopupCallback mPopupCallback;
private boolean mAllowTextWithIcon;
private boolean mExpandedFormat;
@@ -60,13 +65,17 @@ public class ActionMenuItemView extends TextView
this(context, attrs, 0);
}
- public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public ActionMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ActionMenuItemView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
final Resources res = context.getResources();
mAllowTextWithIcon = res.getBoolean(
com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.ActionMenuItemView, 0, 0);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.ActionMenuItemView, defStyleAttr, defStyleRes);
mMinWidth = a.getDimensionPixelSize(
com.android.internal.R.styleable.ActionMenuItemView_minWidth, 0);
a.recycle();
@@ -99,6 +108,7 @@ public class ActionMenuItemView extends TextView
return mItemData;
}
+ @Override
public void initialize(MenuItemImpl itemData, int menuType) {
mItemData = itemData;
@@ -108,8 +118,24 @@ public class ActionMenuItemView extends TextView
setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
setEnabled(itemData.isEnabled());
+
+ if (itemData.hasSubMenu()) {
+ if (mForwardingListener == null) {
+ mForwardingListener = new ActionMenuItemForwardingListener();
+ }
+ }
}
+ @Override
+ public boolean onTouchEvent(MotionEvent e) {
+ if (mItemData.hasSubMenu() && mForwardingListener != null
+ && mForwardingListener.onTouch(this, e)) {
+ return true;
+ }
+ return super.onTouchEvent(e);
+ }
+
+ @Override
public void onClick(View v) {
if (mItemInvoker != null) {
mItemInvoker.invokeItem(mItemData);
@@ -120,6 +146,10 @@ public class ActionMenuItemView extends TextView
mItemInvoker = invoker;
}
+ public void setPopupCallback(PopupCallback popupCallback) {
+ mPopupCallback = popupCallback;
+ }
+
public boolean prefersCondensedTitle() {
return true;
}
@@ -252,11 +282,6 @@ public class ActionMenuItemView extends TextView
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
- // Fill all available height.
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(
- MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
- }
final boolean textVisible = hasText();
if (textVisible && mSavedPaddingLeft >= 0) {
super.setPadding(mSavedPaddingLeft, getPaddingTop(),
@@ -285,4 +310,42 @@ public class ActionMenuItemView extends TextView
super.setPadding((w - dw) / 2, getPaddingTop(), getPaddingRight(), getPaddingBottom());
}
}
+
+ private class ActionMenuItemForwardingListener extends ForwardingListener {
+ public ActionMenuItemForwardingListener() {
+ super(ActionMenuItemView.this);
+ }
+
+ @Override
+ public ListPopupWindow getPopup() {
+ if (mPopupCallback != null) {
+ return mPopupCallback.getPopup();
+ }
+ return null;
+ }
+
+ @Override
+ protected boolean onForwardingStarted() {
+ // Call the invoker, then check if the expected popup is showing.
+ if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) {
+ final ListPopupWindow popup = getPopup();
+ return popup != null && popup.isShowing();
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean onForwardingStopped() {
+ final ListPopupWindow popup = getPopup();
+ if (popup != null) {
+ popup.dismiss();
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public static abstract class PopupCallback {
+ public abstract ListPopupWindow getPopup();
+ }
}
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
index 5d0b25f..de5e279 100644
--- a/core/java/com/android/internal/view/menu/IconMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -57,8 +57,8 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
private static String sPrependShortcutLabel;
- public IconMenuItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs);
+ public IconMenuItemView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
if (sPrependShortcutLabel == null) {
/*
@@ -68,10 +68,9 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
sPrependShortcutLabel = getResources().getString(
com.android.internal.R.string.prepend_shortcut_label);
}
-
- TypedArray a =
- context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.MenuView, defStyle, 0);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.MenuView, defStyleAttr, defStyleRes);
mDisabledAlpha = a.getFloat(
com.android.internal.R.styleable.MenuView_itemIconDisabledAlpha, 0.8f);
@@ -81,7 +80,11 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
a.recycle();
}
-
+
+ public IconMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
public IconMenuItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index a2a4acc..692bdac 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -55,13 +55,13 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
private boolean mForceShowIcon;
- public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs);
-
- TypedArray a =
- context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.MenuView, defStyle, 0);
-
+ public ListMenuItemView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.MenuView, defStyleAttr, defStyleRes);
+
mBackground = a.getDrawable(com.android.internal.R.styleable.MenuView_itemBackground);
mTextAppearance = a.getResourceId(com.android.internal.R.styleable.
MenuView_itemTextAppearance, -1);
@@ -72,6 +72,10 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
a.recycle();
}
+ public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
public ListMenuItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
index e1bb3621..c476354 100644
--- a/core/java/com/android/internal/view/menu/ListMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
@@ -17,7 +17,6 @@
package com.android.internal.view.menu;
import android.content.Context;
-import android.database.DataSetObserver;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.SparseArray;
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 5464284..5d7d322 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -926,7 +926,7 @@ public class MenuBuilder implements Menu {
* sub menu is about to be shown, <var>allMenusAreClosing</var>
* is false.
*/
- final void close(boolean allMenusAreClosing) {
+ public final void close(boolean allMenusAreClosing) {
if (mIsClosing) return;
mIsClosing = true;
@@ -953,7 +953,7 @@ public class MenuBuilder implements Menu {
* false if only item properties changed.
* (Visibility is a structural property since it affects layout.)
*/
- void onItemsChanged(boolean structureChanged) {
+ public void onItemsChanged(boolean structureChanged) {
if (!mPreventDispatchingItemsChanged) {
if (structureChanged) {
mIsVisibleItemsStale = true;
@@ -1007,7 +1007,7 @@ public class MenuBuilder implements Menu {
onItemsChanged(true);
}
- ArrayList<MenuItemImpl> getVisibleItems() {
+ public ArrayList<MenuItemImpl> getVisibleItems() {
if (!mIsVisibleItemsStale) return mVisibleItems;
// Refresh the visible items
@@ -1092,12 +1092,12 @@ public class MenuBuilder implements Menu {
mIsActionItemsStale = false;
}
- ArrayList<MenuItemImpl> getActionItems() {
+ public ArrayList<MenuItemImpl> getActionItems() {
flagActionItems();
return mActionItems;
}
- ArrayList<MenuItemImpl> getNonActionItems() {
+ public ArrayList<MenuItemImpl> getNonActionItems() {
flagActionItems();
return mNonActionItems;
}
@@ -1128,7 +1128,7 @@ public class MenuBuilder implements Menu {
}
if (iconRes > 0) {
- mHeaderIcon = r.getDrawable(iconRes);
+ mHeaderIcon = getContext().getDrawable(iconRes);
} else if (icon != null) {
mHeaderIcon = icon;
}
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 4d0a326..61dcaca 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -385,7 +385,7 @@ public final class MenuItemImpl implements MenuItem {
}
if (mIconResId != NO_ICON) {
- Drawable icon = mMenu.getResources().getDrawable(mIconResId);
+ Drawable icon = mMenu.getContext().getDrawable(mIconResId);
mIconResId = NO_ICON;
mIconDrawable = icon;
return icon;
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 05e9a66..d664058 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -23,7 +23,6 @@ import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuItem;
-import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java
index f3891c7..183478f 100644
--- a/core/java/com/android/internal/widget/AbsActionBarView.java
+++ b/core/java/com/android/internal/widget/AbsActionBarView.java
@@ -16,8 +16,8 @@
package com.android.internal.widget;
import com.android.internal.R;
-import com.android.internal.view.menu.ActionMenuPresenter;
-import com.android.internal.view.menu.ActionMenuView;
+import android.widget.ActionMenuPresenter;
+import android.widget.ActionMenuView;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -47,15 +47,20 @@ public abstract class AbsActionBarView extends ViewGroup {
private static final int FADE_DURATION = 200;
public AbsActionBarView(Context context) {
- super(context);
+ this(context, null);
}
public AbsActionBarView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
}
- public AbsActionBarView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public AbsActionBarView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AbsActionBarView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
@@ -95,9 +100,6 @@ public abstract class AbsActionBarView extends ViewGroup {
public void setContentHeight(int height) {
mContentHeight = height;
- if (mMenuView != null) {
- mMenuView.setMaxItemHeight(mContentHeight);
- }
requestLayout();
}
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index 8a49899..ed07514 100644
--- a/core/java/com/android/internal/widget/ActionBarContainer.java
+++ b/core/java/com/android/internal/widget/ActionBarContainer.java
@@ -16,10 +16,10 @@
package com.android.internal.widget;
-import android.app.ActionBar;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.graphics.ColorFilter;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ActionMode;
@@ -43,6 +43,7 @@ public class ActionBarContainer extends FrameLayout {
private Drawable mSplitBackground;
private boolean mIsSplit;
private boolean mIsStacked;
+ private int mHeight;
public ActionBarContainer(Context context) {
this(context, null);
@@ -51,13 +52,15 @@ public class ActionBarContainer extends FrameLayout {
public ActionBarContainer(Context context, AttributeSet attrs) {
super(context, attrs);
- setBackgroundDrawable(null);
+ // Set a transparent background so that we project appropriately.
+ setBackground(new ActionBarBackgroundDrawable());
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.ActionBar);
mBackground = a.getDrawable(com.android.internal.R.styleable.ActionBar_background);
mStackedBackground = a.getDrawable(
com.android.internal.R.styleable.ActionBar_backgroundStacked);
+ mHeight = a.getDimensionPixelSize(com.android.internal.R.styleable.ActionBar_height, -1);
if (getId() == com.android.internal.R.id.split_action_bar) {
mIsSplit = true;
@@ -243,24 +246,6 @@ public class ActionBarContainer extends FrameLayout {
}
@Override
- public void onDraw(Canvas canvas) {
- if (getWidth() == 0 || getHeight() == 0) {
- return;
- }
-
- if (mIsSplit) {
- if (mSplitBackground != null) mSplitBackground.draw(canvas);
- } else {
- if (mBackground != null) {
- mBackground.draw(canvas);
- }
- if (mStackedBackground != null && mIsStacked) {
- mStackedBackground.draw(canvas);
- }
- }
- }
-
- @Override
public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
// No starting an action mode for an action bar child! (Where would it go?)
return null;
@@ -268,6 +253,11 @@ public class ActionBarContainer extends FrameLayout {
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (mActionBarView == null &&
+ MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST && mHeight >= 0) {
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ Math.min(mHeight, MeasureSpec.getSize(heightMeasureSpec)), MeasureSpec.AT_MOST);
+ }
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mActionBarView == null) return;
@@ -291,12 +281,13 @@ public class ActionBarContainer extends FrameLayout {
public void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
- final boolean hasTabs = mTabContainer != null && mTabContainer.getVisibility() != GONE;
+ final View tabContainer = mTabContainer;
+ final boolean hasTabs = tabContainer != null && tabContainer.getVisibility() != GONE;
- if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
+ if (tabContainer != null && tabContainer.getVisibility() != GONE) {
final int containerHeight = getMeasuredHeight();
- final int tabHeight = mTabContainer.getMeasuredHeight();
- mTabContainer.layout(l, containerHeight - tabHeight, r, containerHeight);
+ final int tabHeight = tabContainer.getMeasuredHeight();
+ tabContainer.layout(l, containerHeight - tabHeight, r, containerHeight);
}
boolean needsInvalidate = false;
@@ -307,13 +298,15 @@ public class ActionBarContainer extends FrameLayout {
}
} else {
if (mBackground != null) {
- mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
- mActionBarView.getRight(), mActionBarView.getBottom());
+ final ActionBarView actionBarView = mActionBarView;
+ mBackground.setBounds(actionBarView.getLeft(), actionBarView.getTop(),
+ actionBarView.getRight(), actionBarView.getBottom());
needsInvalidate = true;
}
- if ((mIsStacked = hasTabs && mStackedBackground != null)) {
- mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(),
- mTabContainer.getRight(), mTabContainer.getBottom());
+ mIsStacked = hasTabs;
+ if (hasTabs && mStackedBackground != null) {
+ mStackedBackground.setBounds(tabContainer.getLeft(), tabContainer.getTop(),
+ tabContainer.getRight(), tabContainer.getBottom());
needsInvalidate = true;
}
}
@@ -322,4 +315,37 @@ public class ActionBarContainer extends FrameLayout {
invalidate();
}
}
+
+ /**
+ * Dummy drawable so that we don't break background display lists and
+ * projection surfaces.
+ */
+ private class ActionBarBackgroundDrawable extends Drawable {
+ @Override
+ public void draw(Canvas canvas) {
+ if (mIsSplit) {
+ if (mSplitBackground != null) mSplitBackground.draw(canvas);
+ } else {
+ if (mBackground != null) {
+ mBackground.draw(canvas);
+ }
+ if (mStackedBackground != null && mIsStacked) {
+ mStackedBackground.draw(canvas);
+ }
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index 8bc1081..e10070f 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -16,8 +16,8 @@
package com.android.internal.widget;
import com.android.internal.R;
-import com.android.internal.view.menu.ActionMenuPresenter;
-import com.android.internal.view.menu.ActionMenuView;
+import android.widget.ActionMenuPresenter;
+import android.widget.ActionMenuView;
import com.android.internal.view.menu.MenuBuilder;
import android.animation.Animator;
@@ -25,7 +25,6 @@ import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
@@ -74,10 +73,16 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi
this(context, attrs, com.android.internal.R.attr.actionModeStyle);
}
- public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionMode, defStyle, 0);
+ public ActionBarContextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ActionBarContextView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.ActionMode, defStyleAttr, defStyleRes);
setBackgroundDrawable(a.getDrawable(
com.android.internal.R.styleable.ActionMode_background));
mTitleStyleRes = a.getResourceId(
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index c957b67..01bee0c 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -16,18 +16,22 @@
package com.android.internal.widget;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import com.android.internal.app.ActionBarImpl;
-
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Canvas;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.util.AttributeSet;
+import android.util.IntProperty;
+import android.util.Property;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
+import android.view.WindowInsets;
+import android.widget.OverScroller;
/**
* Special layout for the containing of an overlay action bar (and its
@@ -38,7 +42,7 @@ public class ActionBarOverlayLayout extends ViewGroup {
private static final String TAG = "ActionBarOverlayLayout";
private int mActionBarHeight;
- private ActionBarImpl mActionBar;
+ //private WindowDecorActionBar mActionBar;
private int mWindowVisibility = View.VISIBLE;
// The main UI elements that we handle the layout of.
@@ -54,6 +58,10 @@ public class ActionBarOverlayLayout extends ViewGroup {
private boolean mIgnoreWindowContentOverlay;
private boolean mOverlayMode;
+ private boolean mHasNonEmbeddedTabs;
+ private boolean mHideOnContentScroll;
+ private boolean mAnimatingForFling;
+ private int mHideOnContentScrollReference;
private int mLastSystemUiVisibility;
private final Rect mBaseContentInsets = new Rect();
private final Rect mLastBaseContentInsets = new Rect();
@@ -62,6 +70,84 @@ public class ActionBarOverlayLayout extends ViewGroup {
private final Rect mInnerInsets = new Rect();
private final Rect mLastInnerInsets = new Rect();
+ private ActionBarVisibilityCallback mActionBarVisibilityCallback;
+
+ private final int ACTION_BAR_ANIMATE_DELAY = 600; // ms
+
+ private OverScroller mFlingEstimator;
+
+ private ViewPropertyAnimator mCurrentActionBarTopAnimator;
+ private ViewPropertyAnimator mCurrentActionBarBottomAnimator;
+
+ private final Animator.AnimatorListener mTopAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCurrentActionBarTopAnimator = null;
+ mAnimatingForFling = false;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCurrentActionBarTopAnimator = null;
+ mAnimatingForFling = false;
+ }
+ };
+
+ private final Animator.AnimatorListener mBottomAnimatorListener =
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCurrentActionBarBottomAnimator = null;
+ mAnimatingForFling = false;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCurrentActionBarBottomAnimator = null;
+ mAnimatingForFling = false;
+ }
+ };
+
+ private final Runnable mRemoveActionBarHideOffset = new Runnable() {
+ public void run() {
+ haltActionBarHideOffsetAnimations();
+ mCurrentActionBarTopAnimator = mActionBarTop.animate().translationY(0)
+ .setListener(mTopAnimatorListener);
+ if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
+ mCurrentActionBarBottomAnimator = mActionBarBottom.animate().translationY(0)
+ .setListener(mBottomAnimatorListener);
+ }
+ }
+ };
+
+ private final Runnable mAddActionBarHideOffset = new Runnable() {
+ public void run() {
+ haltActionBarHideOffsetAnimations();
+ mCurrentActionBarTopAnimator = mActionBarTop.animate()
+ .translationY(-mActionBarTop.getHeight())
+ .setListener(mTopAnimatorListener);
+ if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
+ mCurrentActionBarBottomAnimator = mActionBarBottom.animate()
+ .translationY(mActionBarBottom.getHeight())
+ .setListener(mBottomAnimatorListener);
+ }
+ }
+ };
+
+ public static final Property<ActionBarOverlayLayout, Integer> ACTION_BAR_HIDE_OFFSET =
+ new IntProperty<ActionBarOverlayLayout>("actionBarHideOffset") {
+
+ @Override
+ public void setValue(ActionBarOverlayLayout object, int value) {
+ object.setActionBarHideOffset(value);
+ }
+
+ @Override
+ public Integer get(ActionBarOverlayLayout object) {
+ return object.getActionBarHideOffset();
+ }
+ };
+
static final int[] ATTRS = new int [] {
com.android.internal.R.attr.actionBarSize,
com.android.internal.R.attr.windowContentOverlay
@@ -86,14 +172,22 @@ public class ActionBarOverlayLayout extends ViewGroup {
mIgnoreWindowContentOverlay = context.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT;
+
+ mFlingEstimator = new OverScroller(context);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ haltActionBarHideOffsetAnimations();
}
- public void setActionBar(ActionBarImpl impl) {
- mActionBar = impl;
+ public void setActionBarVisibilityCallback(ActionBarVisibilityCallback cb) {
+ mActionBarVisibilityCallback = cb;
if (getWindowToken() != null) {
// This is being initialized after being added to a window;
// make sure to update all state now.
- mActionBar.setWindowVisibility(mWindowVisibility);
+ mActionBarVisibilityCallback.onWindowVisibilityChanged(mWindowVisibility);
if (mLastSystemUiVisibility != 0) {
int newVis = mLastSystemUiVisibility;
onWindowSystemUiVisibilityChanged(newVis);
@@ -114,6 +208,14 @@ public class ActionBarOverlayLayout extends ViewGroup {
Build.VERSION_CODES.KITKAT;
}
+ public boolean isInOverlayMode() {
+ return mOverlayMode;
+ }
+
+ public void setHasNonEmbeddedTabs(boolean hasNonEmbeddedTabs) {
+ mHasNonEmbeddedTabs = hasNonEmbeddedTabs;
+ }
+
public void setShowingForActionMode(boolean showing) {
if (showing) {
// Here's a fun hack: if the status bar is currently being hidden,
@@ -140,19 +242,18 @@ public class ActionBarOverlayLayout extends ViewGroup {
pullChildren();
final int diff = mLastSystemUiVisibility ^ visible;
mLastSystemUiVisibility = visible;
- final boolean barVisible = (visible&SYSTEM_UI_FLAG_FULLSCREEN) == 0;
- final boolean wasVisible = mActionBar != null ? mActionBar.isSystemShowing() : true;
- final boolean stable = (visible&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
- if (mActionBar != null) {
+ final boolean barVisible = (visible & SYSTEM_UI_FLAG_FULLSCREEN) == 0;
+ final boolean stable = (visible & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
+ if (mActionBarVisibilityCallback != null) {
// We want the bar to be visible if it is not being hidden,
// or the app has not turned on a stable UI mode (meaning they
// are performing explicit layout around the action bar).
- mActionBar.enableContentAnimations(!stable);
- if (barVisible || !stable) mActionBar.showForSystem();
- else mActionBar.hideForSystem();
+ mActionBarVisibilityCallback.enableContentAnimations(!stable);
+ if (barVisible || !stable) mActionBarVisibilityCallback.showForSystem();
+ else mActionBarVisibilityCallback.hideForSystem();
}
- if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
- if (mActionBar != null) {
+ if ((diff & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
+ if (mActionBarVisibilityCallback != null) {
requestApplyInsets();
}
}
@@ -162,8 +263,8 @@ public class ActionBarOverlayLayout extends ViewGroup {
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
mWindowVisibility = visibility;
- if (mActionBar != null) {
- mActionBar.setWindowVisibility(visibility);
+ if (mActionBarVisibilityCallback != null) {
+ mActionBarVisibilityCallback.onWindowVisibilityChanged(visibility);
}
}
@@ -279,14 +380,14 @@ public class ActionBarOverlayLayout extends ViewGroup {
// This is the standard space needed for the action bar. For stable measurement,
// we can't depend on the size currently reported by it -- this must remain constant.
topInset = mActionBarHeight;
- if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) {
- View tabs = mActionBarTop.getTabContainer();
+ if (mHasNonEmbeddedTabs) {
+ final View tabs = mActionBarTop.getTabContainer();
if (tabs != null) {
// If tabs are not embedded, increase space on top to account for them.
topInset += mActionBarHeight;
}
}
- } else if (mActionBarTop.getVisibility() == VISIBLE) {
+ } else if (mActionBarTop.getVisibility() != GONE) {
// This is the space needed on top of the window for all of the action bar
// and tabs.
topInset = mActionBarTop.getMeasuredHeight();
@@ -395,16 +496,138 @@ public class ActionBarOverlayLayout extends ViewGroup {
return false;
}
+ @Override
+ public boolean onStartNestedScroll(View child, View target, int axes) {
+ if ((axes & SCROLL_AXIS_VERTICAL) == 0 || mActionBarTop.getVisibility() != VISIBLE) {
+ return false;
+ }
+ return mHideOnContentScroll;
+ }
+
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int axes) {
+ super.onNestedScrollAccepted(child, target, axes);
+ mHideOnContentScrollReference = getActionBarHideOffset();
+ haltActionBarHideOffsetAnimations();
+ if (mActionBarVisibilityCallback != null) {
+ mActionBarVisibilityCallback.onContentScrollStarted();
+ }
+ }
+
+ @Override
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed) {
+ mHideOnContentScrollReference += dyConsumed;
+ setActionBarHideOffset(mHideOnContentScrollReference);
+ }
+
+ @Override
+ public void onStopNestedScroll(View target) {
+ super.onStopNestedScroll(target);
+ if (mHideOnContentScroll && !mAnimatingForFling) {
+ if (mHideOnContentScrollReference <= mActionBarTop.getHeight()) {
+ postRemoveActionBarHideOffset();
+ } else {
+ postAddActionBarHideOffset();
+ }
+ }
+ if (mActionBarVisibilityCallback != null) {
+ mActionBarVisibilityCallback.onContentScrollStopped();
+ }
+ }
+
+ @Override
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+ if (!mHideOnContentScroll || !consumed) {
+ return false;
+ }
+ if (shouldHideActionBarOnFling(velocityX, velocityY)) {
+ addActionBarHideOffset();
+ } else {
+ removeActionBarHideOffset();
+ }
+ mAnimatingForFling = true;
+ return true;
+ }
+
void pullChildren() {
if (mContent == null) {
mContent = findViewById(com.android.internal.R.id.content);
- mActionBarTop = (ActionBarContainer)findViewById(
+ mActionBarTop = (ActionBarContainer) findViewById(
com.android.internal.R.id.action_bar_container);
mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar);
}
}
+ public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
+ if (hideOnContentScroll != mHideOnContentScroll) {
+ mHideOnContentScroll = hideOnContentScroll;
+ if (!hideOnContentScroll) {
+ stopNestedScroll();
+ haltActionBarHideOffsetAnimations();
+ setActionBarHideOffset(0);
+ }
+ }
+ }
+
+ public boolean isHideOnContentScrollEnabled() {
+ return mHideOnContentScroll;
+ }
+
+ public int getActionBarHideOffset() {
+ return -((int) mActionBarTop.getTranslationY());
+ }
+
+ public void setActionBarHideOffset(int offset) {
+ haltActionBarHideOffsetAnimations();
+ final int topHeight = mActionBarTop.getHeight();
+ offset = Math.max(0, Math.min(offset, topHeight));
+ mActionBarTop.setTranslationY(-offset);
+ if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
+ // Match the hide offset proportionally for a split bar
+ final float fOffset = (float) offset / topHeight;
+ final int bOffset = (int) (mActionBarBottom.getHeight() * fOffset);
+ mActionBarBottom.setTranslationY(bOffset);
+ }
+ }
+
+ private void haltActionBarHideOffsetAnimations() {
+ removeCallbacks(mRemoveActionBarHideOffset);
+ removeCallbacks(mAddActionBarHideOffset);
+ if (mCurrentActionBarTopAnimator != null) {
+ mCurrentActionBarTopAnimator.cancel();
+ }
+ if (mCurrentActionBarBottomAnimator != null) {
+ mCurrentActionBarBottomAnimator.cancel();
+ }
+ }
+
+ private void postRemoveActionBarHideOffset() {
+ haltActionBarHideOffsetAnimations();
+ postDelayed(mRemoveActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
+ }
+
+ private void postAddActionBarHideOffset() {
+ haltActionBarHideOffsetAnimations();
+ postDelayed(mAddActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
+ }
+
+ private void removeActionBarHideOffset() {
+ haltActionBarHideOffsetAnimations();
+ mRemoveActionBarHideOffset.run();
+ }
+
+ private void addActionBarHideOffset() {
+ haltActionBarHideOffsetAnimations();
+ mAddActionBarHideOffset.run();
+ }
+
+ private boolean shouldHideActionBarOnFling(float velocityX, float velocityY) {
+ mFlingEstimator.fling(0, 0, 0, (int) velocityY, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
+ final int finalY = mFlingEstimator.getFinalY();
+ return finalY > mActionBarTop.getHeight();
+ }
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
@@ -423,4 +646,13 @@ public class ActionBarOverlayLayout extends ViewGroup {
super(source);
}
}
+
+ public interface ActionBarVisibilityCallback {
+ void onWindowVisibilityChanged(int visibility);
+ void showForSystem();
+ void hideForSystem();
+ void enableContentAnimations(boolean enable);
+ void onContentScrollStarted();
+ void onContentScrollStopped();
+ }
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 786f5cf..60631b9 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -41,6 +41,8 @@ import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.Window;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.ActionMenuPresenter;
+import android.widget.ActionMenuView;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -52,8 +54,6 @@ import android.widget.TextView;
import com.android.internal.R;
import com.android.internal.transition.ActionBarTransition;
import com.android.internal.view.menu.ActionMenuItem;
-import com.android.internal.view.menu.ActionMenuPresenter;
-import com.android.internal.view.menu.ActionMenuView;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuItemImpl;
import com.android.internal.view.menu.MenuPresenter;
@@ -430,6 +430,7 @@ public class ActionBarView extends AbsActionBarView {
mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
// Span the whole width
layoutParams.width = LayoutParams.MATCH_PARENT;
+ layoutParams.height = LayoutParams.WRAP_CONTENT;
configPresenters(builder);
menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
if (mSplitView != null) {
@@ -696,7 +697,7 @@ public class ActionBarView extends AbsActionBarView {
}
public void setIcon(int resId) {
- setIcon(resId != 0 ? mContext.getResources().getDrawable(resId) : null);
+ setIcon(resId != 0 ? mContext.getDrawable(resId) : null);
}
public boolean hasIcon() {
@@ -711,7 +712,7 @@ public class ActionBarView extends AbsActionBarView {
}
public void setLogo(int resId) {
- setLogo(resId != 0 ? mContext.getResources().getDrawable(resId) : null);
+ setLogo(resId != 0 ? mContext.getDrawable(resId) : null);
}
public boolean hasLogo() {
@@ -1416,7 +1417,7 @@ public class ActionBarView extends AbsActionBarView {
public void setUpIndicator(int resId) {
mUpIndicatorRes = resId;
- mUpView.setImageDrawable(resId != 0 ? getResources().getDrawable(resId) : null);
+ mUpView.setImageDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
}
@Override
diff --git a/core/java/com/android/internal/widget/AutoScrollHelper.java b/core/java/com/android/internal/widget/AutoScrollHelper.java
index 7a294aa..0d468ca 100644
--- a/core/java/com/android/internal/widget/AutoScrollHelper.java
+++ b/core/java/com/android/internal/widget/AutoScrollHelper.java
@@ -892,6 +892,10 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
public boolean canTargetScrollVertically(int direction) {
final AbsListView target = mTarget;
final int itemCount = target.getCount();
+ if (itemCount == 0) {
+ return false;
+ }
+
final int childCount = target.getChildCount();
final int firstPosition = target.getFirstVisiblePosition();
final int lastPosition = firstPosition + childCount;
diff --git a/core/java/com/android/internal/widget/DialogTitle.java b/core/java/com/android/internal/widget/DialogTitle.java
index b86c438..7ea3d6b 100644
--- a/core/java/com/android/internal/widget/DialogTitle.java
+++ b/core/java/com/android/internal/widget/DialogTitle.java
@@ -28,10 +28,13 @@ import android.widget.TextView;
* the text to the available space.
*/
public class DialogTitle extends TextView {
-
- public DialogTitle(Context context, AttributeSet attrs,
- int defStyle) {
- super(context, attrs, defStyle);
+
+ public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
}
public DialogTitle(Context context, AttributeSet attrs) {
diff --git a/core/java/com/android/internal/widget/FaceUnlockView.java b/core/java/com/android/internal/widget/FaceUnlockView.java
index e3c1247..121e601 100644
--- a/core/java/com/android/internal/widget/FaceUnlockView.java
+++ b/core/java/com/android/internal/widget/FaceUnlockView.java
@@ -18,8 +18,6 @@ package com.android.internal.widget;
import android.content.Context;
import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
import android.widget.RelativeLayout;
public class FaceUnlockView extends RelativeLayout {
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 91056f1..9501f92 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -28,6 +28,7 @@ interface ILockSettings {
boolean checkPattern(in String pattern, int userId);
void setLockPassword(in String password, int userId);
boolean checkPassword(in String password, int userId);
+ boolean checkVoldPassword(int userId);
boolean havePattern(int userId);
boolean havePassword(int userId);
void removeUser(int userId);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 8602260..2882b54 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -19,18 +19,21 @@ package com.android.internal.widget;
import android.Manifest;
import android.app.ActivityManagerNative;
import android.app.admin.DevicePolicyManager;
+import android.app.trust.TrustManager;
import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.os.Binder;
+import android.net.Uri;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.storage.IMountService;
+import android.os.storage.StorageManager;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -46,6 +49,8 @@ import com.google.android.collect.Lists;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
/**
@@ -145,6 +150,8 @@ public class LockPatternUtils {
private static final String LOCK_SCREEN_OWNER_INFO_ENABLED =
Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
+ private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents";
+
private final Context mContext;
private final ContentResolver mContentResolver;
private DevicePolicyManager mDevicePolicyManager;
@@ -167,6 +174,15 @@ public class LockPatternUtils {
return mDevicePolicyManager;
}
+ private TrustManager getTrustManager() {
+ TrustManager trust = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
+ if (trust == null) {
+ Log.e(TAG, "Can't get TrustManagerService: is it running?",
+ new IllegalStateException("Stack trace:"));
+ }
+ return trust;
+ }
+
/**
* @param contentResolver Used to look up and save settings.
*/
@@ -242,10 +258,14 @@ public class LockPatternUtils {
*/
public void reportFailedPasswordAttempt() {
getDevicePolicyManager().reportFailedPasswordAttempt(getCurrentOrCallingUserId());
+ getTrustManager().reportUnlockAttempt(false /* authenticated */,
+ getCurrentOrCallingUserId());
}
public void reportSuccessfulPasswordAttempt() {
getDevicePolicyManager().reportSuccessfulPasswordAttempt(getCurrentOrCallingUserId());
+ getTrustManager().reportUnlockAttempt(true /* authenticated */,
+ getCurrentOrCallingUserId());
}
public void setCurrentUser(int userId) {
@@ -313,6 +333,20 @@ public class LockPatternUtils {
}
/**
+ * Check to see if vold already has the password.
+ * Note that this also clears vold's copy of the password.
+ * @return Whether the vold password matches or not.
+ */
+ public boolean checkVoldPassword() {
+ final int userId = getCurrentOrCallingUserId();
+ try {
+ return getLockSettings().checkVoldPassword(userId);
+ } catch (RemoteException re) {
+ return false;
+ }
+ }
+
+ /**
* Check to see if a password matches any of the passwords stored in the
* password history.
*
@@ -496,38 +530,71 @@ public class LockPatternUtils {
*/
public void saveLockPattern(List<LockPatternView.Cell> pattern, boolean isFallback) {
try {
- getLockSettings().setLockPattern(patternToString(pattern), getCurrentOrCallingUserId());
+ int userId = getCurrentOrCallingUserId();
+ getLockSettings().setLockPattern(patternToString(pattern), userId);
DevicePolicyManager dpm = getDevicePolicyManager();
if (pattern != null) {
+
+ int userHandle = userId;
+ if (userHandle == UserHandle.USER_OWNER) {
+ String stringPattern = patternToString(pattern);
+ updateEncryptionPassword(StorageManager.CRYPT_TYPE_PATTERN, stringPattern);
+ }
+
setBoolean(PATTERN_EVER_CHOSEN_KEY, true);
if (!isFallback) {
deleteGallery();
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING,
- pattern.size(), 0, 0, 0, 0, 0, 0, getCurrentOrCallingUserId());
+ pattern.size(), 0, 0, 0, 0, 0, 0, userId);
} else {
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK);
setLong(PASSWORD_TYPE_ALTERNATE_KEY,
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
finishBiometricWeak();
dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK,
- 0, 0, 0, 0, 0, 0, 0, getCurrentOrCallingUserId());
+ 0, 0, 0, 0, 0, 0, 0, userId);
}
} else {
dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0,
- 0, 0, 0, 0, 0, getCurrentOrCallingUserId());
+ 0, 0, 0, 0, 0, userId);
}
} catch (RemoteException re) {
Log.e(TAG, "Couldn't save lock pattern " + re);
}
}
+ private void updateCryptoUserInfo() {
+ int userId = getCurrentOrCallingUserId();
+ if (userId != UserHandle.USER_OWNER) {
+ return;
+ }
+
+ final String ownerInfo = isOwnerInfoEnabled() ? getOwnerInfo(userId) : "";
+
+ IBinder service = ServiceManager.getService("mount");
+ if (service == null) {
+ Log.e(TAG, "Could not find the mount service to update the user info");
+ return;
+ }
+
+ IMountService mountService = IMountService.Stub.asInterface(service);
+ try {
+ Log.d(TAG, "Setting owner info");
+ mountService.setField("OwnerInfo", ownerInfo);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error changing user info", e);
+ }
+ }
+
public void setOwnerInfo(String info, int userId) {
setString(LOCK_SCREEN_OWNER_INFO, info, userId);
+ updateCryptoUserInfo();
}
public void setOwnerInfoEnabled(boolean enabled) {
setBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, enabled);
+ updateCryptoUserInfo();
}
public String getOwnerInfo(int userId) {
@@ -566,7 +633,7 @@ public class LockPatternUtils {
}
/** Update the encryption password if it is enabled **/
- private void updateEncryptionPassword(String password) {
+ private void updateEncryptionPassword(int type, String password) {
DevicePolicyManager dpm = getDevicePolicyManager();
if (dpm.getStorageEncryptionStatus(getCurrentOrCallingUserId())
!= DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE) {
@@ -581,7 +648,7 @@ public class LockPatternUtils {
IMountService mountService = IMountService.Stub.asInterface(service);
try {
- mountService.changeEncryptionPassword(password);
+ mountService.changeEncryptionPassword(type, password);
} catch (RemoteException e) {
Log.e(TAG, "Error changing encryption password", e);
}
@@ -624,12 +691,15 @@ public class LockPatternUtils {
getLockSettings().setLockPassword(password, userHandle);
DevicePolicyManager dpm = getDevicePolicyManager();
if (password != null) {
+ int computedQuality = computePasswordQuality(password);
+
if (userHandle == UserHandle.USER_OWNER) {
// Update the encryption password.
- updateEncryptionPassword(password);
+ int type = computedQuality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ ? StorageManager.CRYPT_TYPE_PIN : StorageManager.CRYPT_TYPE_PASSWORD;
+ updateEncryptionPassword(type, password);
}
- int computedQuality = computePasswordQuality(password);
if (!isFallback) {
deleteGallery();
setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
@@ -676,8 +746,7 @@ public class LockPatternUtils {
0, 0, 0, 0, 0, 0, 0, userHandle);
}
// Add the password to the password history. We assume all
- // password
- // hashes have the same length for simplicity of implementation.
+ // password hashes have the same length for simplicity of implementation.
String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle);
if (passwordHistory == null) {
passwordHistory = new String();
@@ -696,6 +765,11 @@ public class LockPatternUtils {
}
setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
} else {
+ if (userHandle == UserHandle.USER_OWNER) {
+ // Update the encryption password.
+ updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, password);
+ }
+
dpm.setActivePasswordState(
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0,
userHandle);
@@ -714,13 +788,13 @@ public class LockPatternUtils {
*/
public int getKeyguardStoredPasswordQuality() {
int quality =
- (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+ (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
// If the user has chosen to use weak biometric sensor, then return the backup locking
// method and treat biometric as a special case.
if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
quality =
(int) getLong(PASSWORD_TYPE_ALTERNATE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
}
return quality;
}
@@ -730,7 +804,7 @@ public class LockPatternUtils {
*/
public boolean usingBiometricWeak() {
int quality =
- (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+ (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
return quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
}
@@ -869,11 +943,12 @@ public class LockPatternUtils {
*/
public boolean isLockPatternEnabled() {
final boolean backupEnabled =
- getLong(PASSWORD_TYPE_ALTERNATE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING)
- == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+ getLong(PASSWORD_TYPE_ALTERNATE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED)
+ == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
return getBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, false)
- && (getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING)
+ && (getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED)
== DevicePolicyManager.PASSWORD_QUALITY_SOMETHING ||
(usingBiometricWeak() && backupEnabled));
}
@@ -1201,7 +1276,7 @@ public class LockPatternUtils {
private void setLong(String secureSettingKey, long value, int userHandle) {
try {
- getLockSettings().setLong(secureSettingKey, value, getCurrentOrCallingUserId());
+ getLockSettings().setLong(secureSettingKey, value, userHandle);
} catch (RemoteException re) {
// What can we do?
Log.e(TAG, "Couldn't write long " + secureSettingKey + re);
@@ -1360,4 +1435,38 @@ public class LockPatternUtils {
setBoolean(LOCKSCREEN_WIDGETS_ENABLED, enabled, userId);
}
+ public void setEnabledTrustAgents(Collection<ComponentName> activeTrustAgents) {
+ setEnabledTrustAgents(activeTrustAgents, getCurrentOrCallingUserId());
+ }
+
+ public List<ComponentName> getEnabledTrustAgents() {
+ return getEnabledTrustAgents(getCurrentOrCallingUserId());
+ }
+
+ public void setEnabledTrustAgents(Collection<ComponentName> activeTrustAgents, int userId) {
+ StringBuilder sb = new StringBuilder();
+ for (ComponentName cn : activeTrustAgents) {
+ if (sb.length() > 0) {
+ sb.append(',');
+ }
+ sb.append(cn.flattenToShortString());
+ }
+ setString(ENABLED_TRUST_AGENTS, sb.toString(), userId);
+ getTrustManager().reportEnabledTrustAgentsChanged(getCurrentOrCallingUserId());
+ }
+
+ public List<ComponentName> getEnabledTrustAgents(int userId) {
+ String serialized = getString(ENABLED_TRUST_AGENTS, userId);
+ if (TextUtils.isEmpty(serialized)) {
+ return null;
+ }
+ String[] split = serialized.split(",");
+ ArrayList<ComponentName> activeTrustAgents = new ArrayList<ComponentName>(split.length);
+ for (String s : split) {
+ if (!TextUtils.isEmpty(s)) {
+ activeTrustAgents.add(ComponentName.unflattenFromString(s));
+ }
+ }
+ return activeTrustAgents;
+ }
}
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index b066d70..36ed344 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -32,6 +32,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
@@ -260,13 +261,23 @@ public class LockPatternView extends View {
mPathPaint.setAntiAlias(true);
mPathPaint.setDither(true);
- mPathPaint.setColor(Color.WHITE); // TODO this should be from the style
+
+ int defaultColor = Color.WHITE;
+ TypedValue outValue = new TypedValue();
+ if (context.getTheme().resolveAttribute(android.R.attr.textColorPrimary, outValue, true)) {
+ defaultColor = context.getResources().getColor(outValue.resourceId);
+ }
+
+ final int color = a.getColor(R.styleable.LockPatternView_pathColor, defaultColor);
+ mPathPaint.setColor(color);
+
mPathPaint.setAlpha(mStrokeAlpha);
mPathPaint.setStyle(Paint.Style.STROKE);
mPathPaint.setStrokeJoin(Paint.Join.ROUND);
mPathPaint.setStrokeCap(Paint.Cap.ROUND);
// lot's of bitmaps!
+ // TODO: those bitmaps are hardcoded to the Holo Theme which should not be the case!
mBitmapBtnDefault = getBitmapFor(R.drawable.btn_code_lock_default_holo);
mBitmapBtnTouched = getBitmapFor(R.drawable.btn_code_lock_touched_holo);
mBitmapCircleDefault = getBitmapFor(R.drawable.indicator_code_lock_point_area_default_holo);
@@ -868,12 +879,10 @@ public class LockPatternView extends View {
// TODO: the path should be created and cached every time we hit-detect a cell
// only the last segment of the path should be computed here
- // draw the path of the pattern (unless the user is in progress, and
- // we are in stealth mode)
- final boolean drawPath = (!mInStealthMode || mPatternDisplayMode == DisplayMode.Wrong);
+ // draw the path of the pattern (unless we are in stealth mode)
+ final boolean drawPath = !mInStealthMode;
- // draw the arrows associated with the path (unless the user is in progress, and
- // we are in stealth mode)
+ // draw the arrows associated with the path (unless we are in stealth mode)
boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0;
mPaint.setFilterBitmap(true); // draw with higher quality since we render with transforms
if (drawPath) {
@@ -974,7 +983,7 @@ public class LockPatternView extends View {
Bitmap outerCircle;
Bitmap innerCircle;
- if (!partOfPattern || (mInStealthMode && mPatternDisplayMode != DisplayMode.Wrong)) {
+ if (!partOfPattern || mInStealthMode) {
// unselected circle
outerCircle = mBitmapCircleDefault;
innerCircle = mBitmapBtnDefault;
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboard.java b/core/java/com/android/internal/widget/PasswordEntryKeyboard.java
index 3c01c69..7483e75 100644
--- a/core/java/com/android/internal/widget/PasswordEntryKeyboard.java
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboard.java
@@ -16,7 +16,6 @@
package com.android.internal.widget;
-import java.util.Locale;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -73,8 +72,8 @@ public class PasswordEntryKeyboard extends Keyboard {
private void init(Context context) {
final Resources res = context.getResources();
- mShiftIcon = res.getDrawable(R.drawable.sym_keyboard_shift);
- mShiftLockIcon = res.getDrawable(R.drawable.sym_keyboard_shift_locked);
+ mShiftIcon = context.getDrawable(R.drawable.sym_keyboard_shift);
+ mShiftLockIcon = context.getDrawable(R.drawable.sym_keyboard_shift_locked);
sSpacebarVerticalCorrection = res.getDimensionPixelOffset(
R.dimen.password_keyboard_spacebar_vertical_correction);
}
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
index a3df291..b2c9dc5 100644
--- a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
@@ -21,9 +21,7 @@ import android.content.res.Resources;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.inputmethodservice.KeyboardView.OnKeyboardActionListener;
-import android.os.Handler;
import android.os.SystemClock;
-import android.os.Vibrator;
import android.provider.Settings;
import android.util.Log;
import android.view.HapticFeedbackConstants;
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java
index b37adff..d27346b 100644
--- a/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java
@@ -29,11 +29,16 @@ public class PasswordEntryKeyboardView extends KeyboardView {
static final int KEYCODE_NEXT_LANGUAGE = -104;
public PasswordEntryKeyboardView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
}
- public PasswordEntryKeyboardView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public PasswordEntryKeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public PasswordEntryKeyboardView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index d82831f..e339c44 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -31,11 +31,13 @@ import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.WindowManagerPolicy.PointerEventListener;
import android.view.MotionEvent.PointerCoords;
import java.util.ArrayList;
-public class PointerLocationView extends View implements InputDeviceListener {
+public class PointerLocationView extends View implements InputDeviceListener,
+ PointerEventListener {
private static final String TAG = "Pointer";
// The system property key used to specify an alternate velocity tracker strategy
@@ -520,7 +522,8 @@ public class PointerLocationView extends View implements InputDeviceListener {
.toString());
}
- public void addPointerEvent(MotionEvent event) {
+ @Override
+ public void onPointerEvent(MotionEvent event) {
final int action = event.getAction();
int NP = mPointers.size();
@@ -648,7 +651,7 @@ public class PointerLocationView extends View implements InputDeviceListener {
@Override
public boolean onTouchEvent(MotionEvent event) {
- addPointerEvent(event);
+ onPointerEvent(event);
if (event.getAction() == MotionEvent.ACTION_DOWN && !isFocused()) {
requestFocus();
@@ -660,7 +663,7 @@ public class PointerLocationView extends View implements InputDeviceListener {
public boolean onGenericMotionEvent(MotionEvent event) {
final int source = event.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
- addPointerEvent(event);
+ onPointerEvent(event);
} else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
logMotionEvent("Joystick", event);
} else if ((source & InputDevice.SOURCE_CLASS_POSITION) != 0) {
diff --git a/core/java/com/android/internal/widget/RotarySelector.java b/core/java/com/android/internal/widget/RotarySelector.java
index 4e405f4..64ce918 100644
--- a/core/java/com/android/internal/widget/RotarySelector.java
+++ b/core/java/com/android/internal/widget/RotarySelector.java
@@ -24,7 +24,7 @@ import android.graphics.Paint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
-import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
@@ -35,7 +35,9 @@ import android.view.View;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.animation.DecelerateInterpolator;
+
import static android.view.animation.AnimationUtils.currentAnimationTimeMillis;
+
import com.android.internal.R;
@@ -677,7 +679,7 @@ public class RotarySelector extends View {
mVibrator = (android.os.Vibrator) getContext()
.getSystemService(Context.VIBRATOR_SERVICE);
}
- mVibrator.vibrate(duration);
+ mVibrator.vibrate(duration, AudioManager.STREAM_SYSTEM);
}
}
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
index fa29e6e..d6bd1d6 100644
--- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -23,7 +23,6 @@ import android.animation.TimeInterpolator;
import android.app.ActionBar;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
diff --git a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
index ba113a3..5f3c5f9 100644
--- a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
+++ b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
@@ -79,17 +79,20 @@ public class SizeAdaptiveLayout extends ViewGroup {
private int mModestyPanelTop;
public SizeAdaptiveLayout(Context context) {
- super(context);
- initialize();
+ this(context, null);
}
public SizeAdaptiveLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize();
+ this(context, attrs, 0);
+ }
+
+ public SizeAdaptiveLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
- public SizeAdaptiveLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public SizeAdaptiveLayout(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
initialize();
}
@@ -104,8 +107,6 @@ public class SizeAdaptiveLayout extends ViewGroup {
}
if (background instanceof ColorDrawable) {
mModestyPanel.setBackgroundDrawable(background);
- } else {
- mModestyPanel.setBackgroundColor(Color.BLACK);
}
SizeAdaptiveLayout.LayoutParams layout =
new SizeAdaptiveLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
@@ -151,6 +152,10 @@ public class SizeAdaptiveLayout extends ViewGroup {
if (DEBUG) Log.d(TAG, this + " measure spec: " +
MeasureSpec.toString(heightMeasureSpec));
View model = selectActiveChild(heightMeasureSpec);
+ if (model == null) {
+ setMeasuredDimension(0, 0);
+ return;
+ }
SizeAdaptiveLayout.LayoutParams lp =
(SizeAdaptiveLayout.LayoutParams) model.getLayoutParams();
if (DEBUG) Log.d(TAG, "active min: " + lp.minHeight + " max: " + lp.maxHeight);
@@ -239,6 +244,8 @@ public class SizeAdaptiveLayout extends ViewGroup {
int measureSpec = View.MeasureSpec.makeMeasureSpec(bottom - top,
View.MeasureSpec.EXACTLY);
mActiveChild = selectActiveChild(measureSpec);
+ if (mActiveChild == null) return;
+
mActiveChild.setVisibility(View.VISIBLE);
if (mLastActive != mActiveChild && mLastActive != null) {
diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java
index aebc4f6..deb0fd7 100644
--- a/core/java/com/android/internal/widget/SlidingTab.java
+++ b/core/java/com/android/internal/widget/SlidingTab.java
@@ -21,6 +21,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
@@ -821,7 +822,7 @@ public class SlidingTab extends ViewGroup {
mVibrator = (android.os.Vibrator) getContext()
.getSystemService(Context.VIBRATOR_SERVICE);
}
- mVibrator.vibrate(duration);
+ mVibrator.vibrate(duration, AudioManager.STREAM_SYSTEM);
}
}
diff --git a/core/java/com/android/internal/widget/SubtitleView.java b/core/java/com/android/internal/widget/SubtitleView.java
index 071193c..117463a 100644
--- a/core/java/com/android/internal/widget/SubtitleView.java
+++ b/core/java/com/android/internal/widget/SubtitleView.java
@@ -18,7 +18,6 @@ package com.android.internal.widget;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.res.Resources.Theme;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -33,7 +32,6 @@ import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
-import android.util.TypedValue;
import android.view.View;
import android.view.accessibility.CaptioningManager.CaptionStyle;
@@ -41,6 +39,12 @@ public class SubtitleView extends View {
// Ratio of inner padding to font size.
private static final float INNER_PADDING_RATIO = 0.125f;
+ /** Color used for the shadowed edge of a bevel. */
+ private static final int COLOR_BEVEL_DARK = 0x80000000;
+
+ /** Color used for the illuminated edge of a bevel. */
+ private static final int COLOR_BEVEL_LIGHT = 0x80FFFFFF;
+
// Styled dimensions.
private final float mCornerRadius;
private final float mOutlineWidth;
@@ -79,12 +83,15 @@ public class SubtitleView extends View {
this(context, attrs, 0);
}
- public SubtitleView(Context context, AttributeSet attrs, int defStyle) {
+ public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs);
- final Theme theme = context.getTheme();
- final TypedArray a = theme.obtainStyledAttributes(
- attrs, android.R.styleable.TextView, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes);
CharSequence text = "";
int textSize = 15;
@@ -112,7 +119,6 @@ public class SubtitleView extends View {
// Set up density-dependent properties.
// TODO: Move these to a default style.
final Resources res = getContext().getResources();
- final DisplayMetrics m = res.getDisplayMetrics();
mCornerRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_corner_radius);
mOutlineWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_outline_width);
mShadowRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_shadow_radius);
@@ -311,7 +317,8 @@ public class SubtitleView extends View {
}
}
- if (mEdgeType == CaptionStyle.EDGE_TYPE_OUTLINE) {
+ final int edgeType = mEdgeType;
+ if (edgeType == CaptionStyle.EDGE_TYPE_OUTLINE) {
textPaint.setStrokeJoin(Join.ROUND);
textPaint.setStrokeWidth(mOutlineWidth);
textPaint.setColor(mEdgeColor);
@@ -320,8 +327,24 @@ public class SubtitleView extends View {
for (int i = 0; i < lineCount; i++) {
layout.drawText(c, i, i);
}
- } else if (mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
+ } else if (edgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
textPaint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mEdgeColor);
+ } else if (edgeType == CaptionStyle.EDGE_TYPE_RAISED
+ || edgeType == CaptionStyle.EDGE_TYPE_DEPRESSED) {
+ final boolean raised = edgeType == CaptionStyle.EDGE_TYPE_RAISED;
+ final int colorUp = raised ? Color.WHITE : mEdgeColor;
+ final int colorDown = raised ? mEdgeColor : Color.WHITE;
+ final float offset = mShadowRadius / 2f;
+
+ textPaint.setColor(mForegroundColor);
+ textPaint.setStyle(Style.FILL);
+ textPaint.setShadowLayer(mShadowRadius, -offset, -offset, colorUp);
+
+ for (int i = 0; i < lineCount; i++) {
+ layout.drawText(c, i, i);
+ }
+
+ textPaint.setShadowLayer(mShadowRadius, offset, offset, colorDown);
}
textPaint.setColor(mForegroundColor);
diff --git a/core/java/com/android/internal/widget/TextProgressBar.java b/core/java/com/android/internal/widget/TextProgressBar.java
index e898aa4..7ca07d4 100644
--- a/core/java/com/android/internal/widget/TextProgressBar.java
+++ b/core/java/com/android/internal/widget/TextProgressBar.java
@@ -19,7 +19,6 @@ package com.android.internal.widget;
import android.content.Context;
import android.os.SystemClock;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@@ -59,9 +58,13 @@ public class TextProgressBar extends RelativeLayout implements OnChronometerTick
boolean mChronometerFollow = false;
int mChronometerGravity = Gravity.NO_GRAVITY;
+
+ public TextProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
- public TextProgressBar(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public TextProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
}
public TextProgressBar(Context context, AttributeSet attrs) {
diff --git a/core/java/com/android/internal/widget/WaveView.java b/core/java/com/android/internal/widget/WaveView.java
index d33d50c..86f14b3 100644
--- a/core/java/com/android/internal/widget/WaveView.java
+++ b/core/java/com/android/internal/widget/WaveView.java
@@ -25,10 +25,10 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
+import android.media.AudioManager;
import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
-import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
@@ -583,7 +583,7 @@ public class WaveView extends View implements ValueAnimator.AnimatorUpdateListen
mVibrator = (android.os.Vibrator) getContext()
.getSystemService(Context.VIBRATOR_SERVICE);
}
- mVibrator.vibrate(duration);
+ mVibrator.vibrate(duration, AudioManager.STREAM_SYSTEM);
}
}
diff --git a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
index cd1ccd3..772dc5f 100644
--- a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
+++ b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
@@ -30,6 +30,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.Vibrator;
@@ -234,7 +235,7 @@ public class GlowPadView extends View {
mMagneticTargets = a.getBoolean(R.styleable.GlowPadView_magneticTargets, mMagneticTargets);
int pointId = getResourceId(a, R.styleable.GlowPadView_pointDrawable);
- Drawable pointDrawable = pointId != 0 ? res.getDrawable(pointId) : null;
+ Drawable pointDrawable = pointId != 0 ? context.getDrawable(pointId) : null;
mGlowRadius = a.getDimension(R.styleable.GlowPadView_glowRadius, 0.0f);
TypedValue outValue = new TypedValue();
@@ -564,7 +565,7 @@ public class GlowPadView extends View {
mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1,
UserHandle.USER_CURRENT) != 0;
if (mVibrator != null && hapticEnabled) {
- mVibrator.vibrate(mVibrationDuration);
+ mVibrator.vibrate(mVibrationDuration, AudioManager.STREAM_SYSTEM);
}
}
diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
deleted file mode 100644
index e22d1e8..0000000
--- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
+++ /dev/null
@@ -1,1269 +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 com.android.internal.widget.multiwaveview;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.internal.R;
-
-import java.util.ArrayList;
-
-/**
- * A special widget containing a center and outer ring. Moving the center ring to the outer ring
- * causes an event that can be caught by implementing OnTriggerListener.
- */
-public class MultiWaveView extends View {
- private static final String TAG = "MultiWaveView";
- private static final boolean DEBUG = false;
-
- // Wave state machine
- private static final int STATE_IDLE = 0;
- private static final int STATE_START = 1;
- private static final int STATE_FIRST_TOUCH = 2;
- private static final int STATE_TRACKING = 3;
- private static final int STATE_SNAP = 4;
- private static final int STATE_FINISH = 5;
-
- // Animation properties.
- private static final float SNAP_MARGIN_DEFAULT = 20.0f; // distance to ring before we snap to it
-
- public interface OnTriggerListener {
- int NO_HANDLE = 0;
- int CENTER_HANDLE = 1;
- public void onGrabbed(View v, int handle);
- public void onReleased(View v, int handle);
- public void onTrigger(View v, int target);
- public void onGrabbedStateChange(View v, int handle);
- public void onFinishFinalAnimation();
- }
-
- // Tuneable parameters for animation
- private static final int CHEVRON_INCREMENTAL_DELAY = 160;
- private static final int CHEVRON_ANIMATION_DURATION = 850;
- private static final int RETURN_TO_HOME_DELAY = 1200;
- private static final int RETURN_TO_HOME_DURATION = 200;
- private static final int HIDE_ANIMATION_DELAY = 200;
- private static final int HIDE_ANIMATION_DURATION = 200;
- private static final int SHOW_ANIMATION_DURATION = 200;
- private static final int SHOW_ANIMATION_DELAY = 50;
- private static final int INITIAL_SHOW_HANDLE_DURATION = 200;
-
- private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f;
- private static final float TARGET_SCALE_EXPANDED = 1.0f;
- private static final float TARGET_SCALE_COLLAPSED = 0.8f;
- private static final float RING_SCALE_EXPANDED = 1.0f;
- private static final float RING_SCALE_COLLAPSED = 0.5f;
-
- private TimeInterpolator mChevronAnimationInterpolator = Ease.Quad.easeOut;
-
- private ArrayList<TargetDrawable> mTargetDrawables = new ArrayList<TargetDrawable>();
- private ArrayList<TargetDrawable> mChevronDrawables = new ArrayList<TargetDrawable>();
- private AnimationBundle mChevronAnimations = new AnimationBundle();
- private AnimationBundle mTargetAnimations = new AnimationBundle();
- private AnimationBundle mHandleAnimations = new AnimationBundle();
- private ArrayList<String> mTargetDescriptions;
- private ArrayList<String> mDirectionDescriptions;
- private OnTriggerListener mOnTriggerListener;
- private TargetDrawable mHandleDrawable;
- private TargetDrawable mOuterRing;
- private Vibrator mVibrator;
-
- private int mFeedbackCount = 3;
- private int mVibrationDuration = 0;
- private int mGrabbedState;
- private int mActiveTarget = -1;
- private float mTapRadius;
- private float mWaveCenterX;
- private float mWaveCenterY;
- private int mMaxTargetHeight;
- private int mMaxTargetWidth;
-
- private float mOuterRadius = 0.0f;
- private float mSnapMargin = 0.0f;
- private boolean mDragging;
- private int mNewTargetResources;
-
- private class AnimationBundle extends ArrayList<Tweener> {
- private static final long serialVersionUID = 0xA84D78726F127468L;
- private boolean mSuspended;
-
- public void start() {
- if (mSuspended) return; // ignore attempts to start animations
- final int count = size();
- for (int i = 0; i < count; i++) {
- Tweener anim = get(i);
- anim.animator.start();
- }
- }
-
- public void cancel() {
- final int count = size();
- for (int i = 0; i < count; i++) {
- Tweener anim = get(i);
- anim.animator.cancel();
- }
- clear();
- }
-
- public void stop() {
- final int count = size();
- for (int i = 0; i < count; i++) {
- Tweener anim = get(i);
- anim.animator.end();
- }
- clear();
- }
-
- public void setSuspended(boolean suspend) {
- mSuspended = suspend;
- }
- };
-
- private AnimatorListener mResetListener = new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animator) {
- switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
- dispatchOnFinishFinalAnimation();
- }
- };
-
- private AnimatorListener mResetListenerWithPing = new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animator) {
- ping();
- switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
- dispatchOnFinishFinalAnimation();
- }
- };
-
- private AnimatorUpdateListener mUpdateListener = new AnimatorUpdateListener() {
- public void onAnimationUpdate(ValueAnimator animation) {
- invalidateGlobalRegion(mHandleDrawable);
- invalidate();
- }
- };
-
- private boolean mAnimatingTargets;
- private AnimatorListener mTargetUpdateListener = new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animator) {
- if (mNewTargetResources != 0) {
- internalSetTargetResources(mNewTargetResources);
- mNewTargetResources = 0;
- hideTargets(false, false);
- }
- mAnimatingTargets = false;
- }
- };
- private int mTargetResourceId;
- private int mTargetDescriptionsResourceId;
- private int mDirectionDescriptionsResourceId;
- private boolean mAlwaysTrackFinger;
- private int mHorizontalInset;
- private int mVerticalInset;
- private int mGravity = Gravity.TOP;
- private boolean mInitialLayout = true;
- private Tweener mBackgroundAnimator;
-
- public MultiWaveView(Context context) {
- this(context, null);
- }
-
- public MultiWaveView(Context context, AttributeSet attrs) {
- super(context, attrs);
- Resources res = context.getResources();
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiWaveView);
- mOuterRadius = a.getDimension(R.styleable.MultiWaveView_outerRadius, mOuterRadius);
- mSnapMargin = a.getDimension(R.styleable.MultiWaveView_snapMargin, mSnapMargin);
- mVibrationDuration = a.getInt(R.styleable.MultiWaveView_vibrationDuration,
- mVibrationDuration);
- mFeedbackCount = a.getInt(R.styleable.MultiWaveView_feedbackCount,
- mFeedbackCount);
- mHandleDrawable = new TargetDrawable(res,
- a.peekValue(R.styleable.MultiWaveView_handleDrawable).resourceId);
- mTapRadius = mHandleDrawable.getWidth()/2;
- mOuterRing = new TargetDrawable(res,
- a.peekValue(R.styleable.MultiWaveView_waveDrawable).resourceId);
- mAlwaysTrackFinger = a.getBoolean(R.styleable.MultiWaveView_alwaysTrackFinger, false);
-
- // Read array of chevron drawables
- TypedValue outValue = new TypedValue();
- if (a.getValue(R.styleable.MultiWaveView_chevronDrawables, outValue)) {
- ArrayList<TargetDrawable> chevrons = loadDrawableArray(outValue.resourceId);
- for (int i = 0; i < chevrons.size(); i++) {
- final TargetDrawable chevron = chevrons.get(i);
- for (int k = 0; k < mFeedbackCount; k++) {
- mChevronDrawables.add(chevron == null ? null : new TargetDrawable(chevron));
- }
- }
- }
-
- // Read array of target drawables
- if (a.getValue(R.styleable.MultiWaveView_targetDrawables, outValue)) {
- internalSetTargetResources(outValue.resourceId);
- }
- if (mTargetDrawables == null || mTargetDrawables.size() == 0) {
- throw new IllegalStateException("Must specify at least one target drawable");
- }
-
- // Read array of target descriptions
- if (a.getValue(R.styleable.MultiWaveView_targetDescriptions, outValue)) {
- final int resourceId = outValue.resourceId;
- if (resourceId == 0) {
- throw new IllegalStateException("Must specify target descriptions");
- }
- setTargetDescriptionsResourceId(resourceId);
- }
-
- // Read array of direction descriptions
- if (a.getValue(R.styleable.MultiWaveView_directionDescriptions, outValue)) {
- final int resourceId = outValue.resourceId;
- if (resourceId == 0) {
- throw new IllegalStateException("Must specify direction descriptions");
- }
- setDirectionDescriptionsResourceId(resourceId);
- }
-
- a.recycle();
-
- // Use gravity attribute from LinearLayout
- a = context.obtainStyledAttributes(attrs, android.R.styleable.LinearLayout);
- mGravity = a.getInt(android.R.styleable.LinearLayout_gravity, Gravity.TOP);
- a.recycle();
-
- setVibrateEnabled(mVibrationDuration > 0);
- assignDefaultsIfNeeded();
- }
-
- private void dump() {
- Log.v(TAG, "Outer Radius = " + mOuterRadius);
- Log.v(TAG, "SnapMargin = " + mSnapMargin);
- Log.v(TAG, "FeedbackCount = " + mFeedbackCount);
- Log.v(TAG, "VibrationDuration = " + mVibrationDuration);
- Log.v(TAG, "TapRadius = " + mTapRadius);
- Log.v(TAG, "WaveCenterX = " + mWaveCenterX);
- Log.v(TAG, "WaveCenterY = " + mWaveCenterY);
- }
-
- public void suspendAnimations() {
- mChevronAnimations.setSuspended(true);
- mTargetAnimations.setSuspended(true);
- mHandleAnimations.setSuspended(true);
- }
-
- public void resumeAnimations() {
- mChevronAnimations.setSuspended(false);
- mTargetAnimations.setSuspended(false);
- mHandleAnimations.setSuspended(false);
- mChevronAnimations.start();
- mTargetAnimations.start();
- mHandleAnimations.start();
- }
-
- @Override
- protected int getSuggestedMinimumWidth() {
- // View should be large enough to contain the background + handle and
- // target drawable on either edge.
- return (int) (Math.max(mOuterRing.getWidth(), 2 * mOuterRadius) + mMaxTargetWidth);
- }
-
- @Override
- protected int getSuggestedMinimumHeight() {
- // View should be large enough to contain the unlock ring + target and
- // target drawable on either edge
- return (int) (Math.max(mOuterRing.getHeight(), 2 * mOuterRadius) + mMaxTargetHeight);
- }
-
- private int resolveMeasured(int measureSpec, int desired)
- {
- int result = 0;
- int specSize = MeasureSpec.getSize(measureSpec);
- switch (MeasureSpec.getMode(measureSpec)) {
- case MeasureSpec.UNSPECIFIED:
- result = desired;
- break;
- case MeasureSpec.AT_MOST:
- result = Math.min(specSize, desired);
- break;
- case MeasureSpec.EXACTLY:
- default:
- result = specSize;
- }
- return result;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int minimumWidth = getSuggestedMinimumWidth();
- final int minimumHeight = getSuggestedMinimumHeight();
- int computedWidth = resolveMeasured(widthMeasureSpec, minimumWidth);
- int computedHeight = resolveMeasured(heightMeasureSpec, minimumHeight);
- computeInsets((computedWidth - minimumWidth), (computedHeight - minimumHeight));
- setMeasuredDimension(computedWidth, computedHeight);
- }
-
- private void switchToState(int state, float x, float y) {
- switch (state) {
- case STATE_IDLE:
- deactivateTargets();
- hideTargets(true, false);
- startBackgroundAnimation(0, 0.0f);
- mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
- break;
-
- case STATE_START:
- deactivateHandle(0, 0, 1.0f, null);
- startBackgroundAnimation(0, 0.0f);
- break;
-
- case STATE_FIRST_TOUCH:
- deactivateTargets();
- showTargets(true);
- mHandleDrawable.setState(TargetDrawable.STATE_ACTIVE);
- startBackgroundAnimation(INITIAL_SHOW_HANDLE_DURATION, 1.0f);
- setGrabbedState(OnTriggerListener.CENTER_HANDLE);
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- announceTargets();
- }
- break;
-
- case STATE_TRACKING:
- break;
-
- case STATE_SNAP:
- break;
-
- case STATE_FINISH:
- doFinish();
- break;
- }
- }
-
- private void activateHandle(int duration, int delay, float finalAlpha,
- AnimatorListener finishListener) {
- mHandleAnimations.cancel();
- mHandleAnimations.add(Tweener.to(mHandleDrawable, duration,
- "ease", Ease.Cubic.easeIn,
- "delay", delay,
- "alpha", finalAlpha,
- "onUpdate", mUpdateListener,
- "onComplete", finishListener));
- mHandleAnimations.start();
- }
-
- private void deactivateHandle(int duration, int delay, float finalAlpha,
- AnimatorListener finishListener) {
- mHandleAnimations.cancel();
- mHandleAnimations.add(Tweener.to(mHandleDrawable, duration,
- "ease", Ease.Quart.easeOut,
- "delay", delay,
- "alpha", finalAlpha,
- "x", 0,
- "y", 0,
- "onUpdate", mUpdateListener,
- "onComplete", finishListener));
- mHandleAnimations.start();
- }
-
- /**
- * Animation used to attract user's attention to the target button.
- * Assumes mChevronDrawables is an a list with an even number of chevrons filled with
- * mFeedbackCount items in the order: left, right, top, bottom.
- */
- private void startChevronAnimation() {
- final float chevronStartDistance = mHandleDrawable.getWidth() * 0.8f;
- final float chevronStopDistance = mOuterRadius * 0.9f / 2.0f;
- final float startScale = 0.5f;
- final float endScale = 2.0f;
- final int directionCount = mFeedbackCount > 0 ? mChevronDrawables.size()/mFeedbackCount : 0;
-
- mChevronAnimations.stop();
-
- // Add an animation for all chevron drawables. There are mFeedbackCount drawables
- // in each direction and directionCount directions.
- for (int direction = 0; direction < directionCount; direction++) {
- double angle = 2.0 * Math.PI * direction / directionCount;
- final float sx = (float) Math.cos(angle);
- final float sy = 0.0f - (float) Math.sin(angle);
- final float[] xrange = new float[]
- {sx * chevronStartDistance, sx * chevronStopDistance};
- final float[] yrange = new float[]
- {sy * chevronStartDistance, sy * chevronStopDistance};
- for (int count = 0; count < mFeedbackCount; count++) {
- int delay = count * CHEVRON_INCREMENTAL_DELAY;
- final TargetDrawable icon = mChevronDrawables.get(direction*mFeedbackCount + count);
- if (icon == null) {
- continue;
- }
- mChevronAnimations.add(Tweener.to(icon, CHEVRON_ANIMATION_DURATION,
- "ease", mChevronAnimationInterpolator,
- "delay", delay,
- "x", xrange,
- "y", yrange,
- "alpha", new float[] {1.0f, 0.0f},
- "scaleX", new float[] {startScale, endScale},
- "scaleY", new float[] {startScale, endScale},
- "onUpdate", mUpdateListener));
- }
- }
- mChevronAnimations.start();
- }
-
- private void deactivateTargets() {
- final int count = mTargetDrawables.size();
- for (int i = 0; i < count; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- target.setState(TargetDrawable.STATE_INACTIVE);
- }
- mActiveTarget = -1;
- }
-
- void invalidateGlobalRegion(TargetDrawable drawable) {
- int width = drawable.getWidth();
- int height = drawable.getHeight();
- RectF childBounds = new RectF(0, 0, width, height);
- childBounds.offset(drawable.getX() - width/2, drawable.getY() - height/2);
- View view = this;
- while (view.getParent() != null && view.getParent() instanceof View) {
- view = (View) view.getParent();
- view.getMatrix().mapRect(childBounds);
- view.invalidate((int) Math.floor(childBounds.left),
- (int) Math.floor(childBounds.top),
- (int) Math.ceil(childBounds.right),
- (int) Math.ceil(childBounds.bottom));
- }
- }
-
- /**
- * Dispatches a trigger event to listener. Ignored if a listener is not set.
- * @param whichTarget the target that was triggered.
- */
- private void dispatchTriggerEvent(int whichTarget) {
- vibrate();
- if (mOnTriggerListener != null) {
- mOnTriggerListener.onTrigger(this, whichTarget);
- }
- }
-
- private void dispatchOnFinishFinalAnimation() {
- if (mOnTriggerListener != null) {
- mOnTriggerListener.onFinishFinalAnimation();
- }
- }
-
- private void doFinish() {
- final int activeTarget = mActiveTarget;
- final boolean targetHit = activeTarget != -1;
-
- if (targetHit) {
- if (DEBUG) Log.v(TAG, "Finish with target hit = " + targetHit);
-
- highlightSelected(activeTarget);
-
- // Inform listener of any active targets. Typically only one will be active.
- deactivateHandle(RETURN_TO_HOME_DURATION, RETURN_TO_HOME_DELAY, 0.0f, mResetListener);
- dispatchTriggerEvent(activeTarget);
- if (!mAlwaysTrackFinger) {
- // Force ring and targets to finish animation to final expanded state
- mTargetAnimations.stop();
- }
- } else {
- // Animate handle back to the center based on current state.
- deactivateHandle(HIDE_ANIMATION_DURATION, HIDE_ANIMATION_DELAY, 1.0f,
- mResetListenerWithPing);
- hideTargets(true, false);
- }
-
- setGrabbedState(OnTriggerListener.NO_HANDLE);
- }
-
- private void highlightSelected(int activeTarget) {
- // Highlight the given target and fade others
- mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE);
- hideUnselected(activeTarget);
- }
-
- private void hideUnselected(int active) {
- for (int i = 0; i < mTargetDrawables.size(); i++) {
- if (i != active) {
- mTargetDrawables.get(i).setAlpha(0.0f);
- }
- }
- }
-
- private void hideTargets(boolean animate, boolean expanded) {
- mTargetAnimations.cancel();
- // Note: these animations should complete at the same time so that we can swap out
- // the target assets asynchronously from the setTargetResources() call.
- mAnimatingTargets = animate;
- final int duration = animate ? HIDE_ANIMATION_DURATION : 0;
- final int delay = animate ? HIDE_ANIMATION_DELAY : 0;
-
- final float targetScale = expanded ? TARGET_SCALE_EXPANDED : TARGET_SCALE_COLLAPSED;
- final int length = mTargetDrawables.size();
- for (int i = 0; i < length; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- target.setState(TargetDrawable.STATE_INACTIVE);
- mTargetAnimations.add(Tweener.to(target, duration,
- "ease", Ease.Cubic.easeOut,
- "alpha", 0.0f,
- "scaleX", targetScale,
- "scaleY", targetScale,
- "delay", delay,
- "onUpdate", mUpdateListener));
- }
-
- final float ringScaleTarget = expanded ? RING_SCALE_EXPANDED : RING_SCALE_COLLAPSED;
- mTargetAnimations.add(Tweener.to(mOuterRing, duration,
- "ease", Ease.Cubic.easeOut,
- "alpha", 0.0f,
- "scaleX", ringScaleTarget,
- "scaleY", ringScaleTarget,
- "delay", delay,
- "onUpdate", mUpdateListener,
- "onComplete", mTargetUpdateListener));
-
- mTargetAnimations.start();
- }
-
- private void showTargets(boolean animate) {
- mTargetAnimations.stop();
- mAnimatingTargets = animate;
- final int delay = animate ? SHOW_ANIMATION_DELAY : 0;
- final int duration = animate ? SHOW_ANIMATION_DURATION : 0;
- final int length = mTargetDrawables.size();
- for (int i = 0; i < length; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- target.setState(TargetDrawable.STATE_INACTIVE);
- mTargetAnimations.add(Tweener.to(target, duration,
- "ease", Ease.Cubic.easeOut,
- "alpha", 1.0f,
- "scaleX", 1.0f,
- "scaleY", 1.0f,
- "delay", delay,
- "onUpdate", mUpdateListener));
- }
- mTargetAnimations.add(Tweener.to(mOuterRing, duration,
- "ease", Ease.Cubic.easeOut,
- "alpha", 1.0f,
- "scaleX", 1.0f,
- "scaleY", 1.0f,
- "delay", delay,
- "onUpdate", mUpdateListener,
- "onComplete", mTargetUpdateListener));
-
- mTargetAnimations.start();
- }
-
- private void vibrate() {
- final boolean hapticEnabled = Settings.System.getIntForUser(
- mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1,
- UserHandle.USER_CURRENT) != 0;
- if (mVibrator != null && hapticEnabled) {
- mVibrator.vibrate(mVibrationDuration);
- }
- }
-
- private ArrayList<TargetDrawable> loadDrawableArray(int resourceId) {
- Resources res = getContext().getResources();
- TypedArray array = res.obtainTypedArray(resourceId);
- final int count = array.length();
- ArrayList<TargetDrawable> drawables = new ArrayList<TargetDrawable>(count);
- for (int i = 0; i < count; i++) {
- TypedValue value = array.peekValue(i);
- TargetDrawable target = new TargetDrawable(res, value != null ? value.resourceId : 0);
- drawables.add(target);
- }
- array.recycle();
- return drawables;
- }
-
- private void internalSetTargetResources(int resourceId) {
- mTargetDrawables = loadDrawableArray(resourceId);
- mTargetResourceId = resourceId;
- final int count = mTargetDrawables.size();
- int maxWidth = mHandleDrawable.getWidth();
- int maxHeight = mHandleDrawable.getHeight();
- for (int i = 0; i < count; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- maxWidth = Math.max(maxWidth, target.getWidth());
- maxHeight = Math.max(maxHeight, target.getHeight());
- }
- if (mMaxTargetWidth != maxWidth || mMaxTargetHeight != maxHeight) {
- mMaxTargetWidth = maxWidth;
- mMaxTargetHeight = maxHeight;
- requestLayout(); // required to resize layout and call updateTargetPositions()
- } else {
- updateTargetPositions(mWaveCenterX, mWaveCenterY);
- updateChevronPositions(mWaveCenterX, mWaveCenterY);
- }
- }
-
- /**
- * Loads an array of drawables from the given resourceId.
- *
- * @param resourceId
- */
- public void setTargetResources(int resourceId) {
- if (mAnimatingTargets) {
- // postpone this change until we return to the initial state
- mNewTargetResources = resourceId;
- } else {
- internalSetTargetResources(resourceId);
- }
- }
-
- public int getTargetResourceId() {
- return mTargetResourceId;
- }
-
- /**
- * Sets the resource id specifying the target descriptions for accessibility.
- *
- * @param resourceId The resource id.
- */
- public void setTargetDescriptionsResourceId(int resourceId) {
- mTargetDescriptionsResourceId = resourceId;
- if (mTargetDescriptions != null) {
- mTargetDescriptions.clear();
- }
- }
-
- /**
- * Gets the resource id specifying the target descriptions for accessibility.
- *
- * @return The resource id.
- */
- public int getTargetDescriptionsResourceId() {
- return mTargetDescriptionsResourceId;
- }
-
- /**
- * Sets the resource id specifying the target direction descriptions for accessibility.
- *
- * @param resourceId The resource id.
- */
- public void setDirectionDescriptionsResourceId(int resourceId) {
- mDirectionDescriptionsResourceId = resourceId;
- if (mDirectionDescriptions != null) {
- mDirectionDescriptions.clear();
- }
- }
-
- /**
- * Gets the resource id specifying the target direction descriptions.
- *
- * @return The resource id.
- */
- public int getDirectionDescriptionsResourceId() {
- return mDirectionDescriptionsResourceId;
- }
-
- /**
- * Enable or disable vibrate on touch.
- *
- * @param enabled
- */
- public void setVibrateEnabled(boolean enabled) {
- if (enabled && mVibrator == null) {
- mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
- } else {
- mVibrator = null;
- }
- }
-
- /**
- * Starts chevron animation. Example use case: show chevron animation whenever the phone rings
- * or the user touches the screen.
- *
- */
- public void ping() {
- startChevronAnimation();
- }
-
- /**
- * Resets the widget to default state and cancels all animation. If animate is 'true', will
- * animate objects into place. Otherwise, objects will snap back to place.
- *
- * @param animate
- */
- public void reset(boolean animate) {
- mChevronAnimations.stop();
- mHandleAnimations.stop();
- mTargetAnimations.stop();
- startBackgroundAnimation(0, 0.0f);
- hideChevrons();
- hideTargets(animate, false);
- deactivateHandle(0, 0, 1.0f, null);
- Tweener.reset();
- }
-
- private void startBackgroundAnimation(int duration, float alpha) {
- Drawable background = getBackground();
- if (mAlwaysTrackFinger && background != null) {
- if (mBackgroundAnimator != null) {
- mBackgroundAnimator.animator.end();
- }
- mBackgroundAnimator = Tweener.to(background, duration,
- "ease", Ease.Cubic.easeIn,
- "alpha", new int[] {0, (int)(255.0f * alpha)},
- "delay", SHOW_ANIMATION_DELAY);
- mBackgroundAnimator.animator.start();
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- final int action = event.getAction();
- boolean handled = false;
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- if (DEBUG) Log.v(TAG, "*** DOWN ***");
- handleDown(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (DEBUG) Log.v(TAG, "*** MOVE ***");
- handleMove(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_UP:
- if (DEBUG) Log.v(TAG, "*** UP ***");
- handleMove(event);
- handleUp(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_CANCEL:
- if (DEBUG) Log.v(TAG, "*** CANCEL ***");
- handleMove(event);
- handleCancel(event);
- handled = true;
- break;
- }
- invalidate();
- return handled ? true : super.onTouchEvent(event);
- }
-
- private void moveHandleTo(float x, float y, boolean animate) {
- mHandleDrawable.setX(x);
- mHandleDrawable.setY(y);
- }
-
- private void handleDown(MotionEvent event) {
- float eventX = event.getX();
- float eventY = event.getY();
- switchToState(STATE_START, eventX, eventY);
- if (!trySwitchToFirstTouchState(eventX, eventY)) {
- mDragging = false;
- ping();
- }
- }
-
- private void handleUp(MotionEvent event) {
- if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE");
- switchToState(STATE_FINISH, event.getX(), event.getY());
- }
-
- private void handleCancel(MotionEvent event) {
- if (DEBUG && mDragging) Log.v(TAG, "** Handle CANCEL");
-
- // We should drop the active target here but it interferes with
- // moving off the screen in the direction of the navigation bar. At some point we may
- // want to revisit how we handle this. For now we'll allow a canceled event to
- // activate the current target.
-
- // mActiveTarget = -1; // Drop the active target if canceled.
-
- switchToState(STATE_FINISH, event.getX(), event.getY());
- }
-
- private void handleMove(MotionEvent event) {
- int activeTarget = -1;
- final int historySize = event.getHistorySize();
- ArrayList<TargetDrawable> targets = mTargetDrawables;
- int ntargets = targets.size();
- float x = 0.0f;
- float y = 0.0f;
- for (int k = 0; k < historySize + 1; k++) {
- float eventX = k < historySize ? event.getHistoricalX(k) : event.getX();
- float eventY = k < historySize ? event.getHistoricalY(k) : event.getY();
- // tx and ty are relative to wave center
- float tx = eventX - mWaveCenterX;
- float ty = eventY - mWaveCenterY;
- float touchRadius = (float) Math.sqrt(dist2(tx, ty));
- final float scale = touchRadius > mOuterRadius ? mOuterRadius / touchRadius : 1.0f;
- float limitX = tx * scale;
- float limitY = ty * scale;
- double angleRad = Math.atan2(-ty, tx);
-
- if (!mDragging) {
- trySwitchToFirstTouchState(eventX, eventY);
- }
-
- if (mDragging) {
- // For multiple targets, snap to the one that matches
- final float snapRadius = mOuterRadius - mSnapMargin;
- final float snapDistance2 = snapRadius * snapRadius;
- // Find first target in range
- for (int i = 0; i < ntargets; i++) {
- TargetDrawable target = targets.get(i);
-
- double targetMinRad = (i - 0.5) * 2 * Math.PI / ntargets;
- double targetMaxRad = (i + 0.5) * 2 * Math.PI / ntargets;
- if (target.isEnabled()) {
- boolean angleMatches =
- (angleRad > targetMinRad && angleRad <= targetMaxRad) ||
- (angleRad + 2 * Math.PI > targetMinRad &&
- angleRad + 2 * Math.PI <= targetMaxRad);
- if (angleMatches && (dist2(tx, ty) > snapDistance2)) {
- activeTarget = i;
- }
- }
- }
- }
- x = limitX;
- y = limitY;
- }
-
- if (!mDragging) {
- return;
- }
-
- if (activeTarget != -1) {
- switchToState(STATE_SNAP, x,y);
- moveHandleTo(x, y, false);
- } else {
- switchToState(STATE_TRACKING, x, y);
- moveHandleTo(x, y, false);
- }
-
- // Draw handle outside parent's bounds
- invalidateGlobalRegion(mHandleDrawable);
-
- if (mActiveTarget != activeTarget) {
- // Defocus the old target
- if (mActiveTarget != -1) {
- TargetDrawable target = targets.get(mActiveTarget);
- if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
- target.setState(TargetDrawable.STATE_INACTIVE);
- }
- }
- // Focus the new target
- if (activeTarget != -1) {
- TargetDrawable target = targets.get(activeTarget);
- if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
- target.setState(TargetDrawable.STATE_FOCUSED);
- }
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- String targetContentDescription = getTargetDescription(activeTarget);
- announceText(targetContentDescription);
- }
- activateHandle(0, 0, 0.0f, null);
- } else {
- activateHandle(0, 0, 1.0f, null);
- }
- }
- mActiveTarget = activeTarget;
- }
-
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
- final int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_HOVER_ENTER:
- event.setAction(MotionEvent.ACTION_DOWN);
- break;
- case MotionEvent.ACTION_HOVER_MOVE:
- event.setAction(MotionEvent.ACTION_MOVE);
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- event.setAction(MotionEvent.ACTION_UP);
- break;
- }
- onTouchEvent(event);
- event.setAction(action);
- }
- return super.onHoverEvent(event);
- }
-
- /**
- * Sets the current grabbed state, and dispatches a grabbed state change
- * event to our listener.
- */
- private void setGrabbedState(int newState) {
- if (newState != mGrabbedState) {
- if (newState != OnTriggerListener.NO_HANDLE) {
- vibrate();
- }
- mGrabbedState = newState;
- if (mOnTriggerListener != null) {
- if (newState == OnTriggerListener.NO_HANDLE) {
- mOnTriggerListener.onReleased(this, OnTriggerListener.CENTER_HANDLE);
- } else {
- mOnTriggerListener.onGrabbed(this, OnTriggerListener.CENTER_HANDLE);
- }
- mOnTriggerListener.onGrabbedStateChange(this, newState);
- }
- }
- }
-
- private boolean trySwitchToFirstTouchState(float x, float y) {
- final float tx = x - mWaveCenterX;
- final float ty = y - mWaveCenterY;
- if (mAlwaysTrackFinger || dist2(tx,ty) <= getScaledTapRadiusSquared()) {
- if (DEBUG) Log.v(TAG, "** Handle HIT");
- switchToState(STATE_FIRST_TOUCH, x, y);
- moveHandleTo(tx, ty, false);
- mDragging = true;
- return true;
- }
- return false;
- }
-
- private void assignDefaultsIfNeeded() {
- if (mOuterRadius == 0.0f) {
- mOuterRadius = Math.max(mOuterRing.getWidth(), mOuterRing.getHeight())/2.0f;
- }
- if (mSnapMargin == 0.0f) {
- mSnapMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- SNAP_MARGIN_DEFAULT, getContext().getResources().getDisplayMetrics());
- }
- }
-
- private void computeInsets(int dx, int dy) {
- final int layoutDirection = getLayoutDirection();
- final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
-
- switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
- case Gravity.LEFT:
- mHorizontalInset = 0;
- break;
- case Gravity.RIGHT:
- mHorizontalInset = dx;
- break;
- case Gravity.CENTER_HORIZONTAL:
- default:
- mHorizontalInset = dx / 2;
- break;
- }
- switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
- case Gravity.TOP:
- mVerticalInset = 0;
- break;
- case Gravity.BOTTOM:
- mVerticalInset = dy;
- break;
- case Gravity.CENTER_VERTICAL:
- default:
- mVerticalInset = dy / 2;
- break;
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- final int width = right - left;
- final int height = bottom - top;
-
- // Target placement width/height. This puts the targets on the greater of the ring
- // width or the specified outer radius.
- final float placementWidth = Math.max(mOuterRing.getWidth(), 2 * mOuterRadius);
- final float placementHeight = Math.max(mOuterRing.getHeight(), 2 * mOuterRadius);
- float newWaveCenterX = mHorizontalInset
- + Math.max(width, mMaxTargetWidth + placementWidth) / 2;
- float newWaveCenterY = mVerticalInset
- + Math.max(height, + mMaxTargetHeight + placementHeight) / 2;
-
- if (mInitialLayout) {
- hideChevrons();
- hideTargets(false, false);
- moveHandleTo(0, 0, false);
- mInitialLayout = false;
- }
-
- mOuterRing.setPositionX(newWaveCenterX);
- mOuterRing.setPositionY(newWaveCenterY);
-
- mHandleDrawable.setPositionX(newWaveCenterX);
- mHandleDrawable.setPositionY(newWaveCenterY);
-
- updateTargetPositions(newWaveCenterX, newWaveCenterY);
- updateChevronPositions(newWaveCenterX, newWaveCenterY);
-
- mWaveCenterX = newWaveCenterX;
- mWaveCenterY = newWaveCenterY;
-
- if (DEBUG) dump();
- }
-
- private void updateTargetPositions(float centerX, float centerY) {
- // Reposition the target drawables if the view changed.
- ArrayList<TargetDrawable> targets = mTargetDrawables;
- final int size = targets.size();
- final float alpha = (float) (-2.0f * Math.PI / size);
- for (int i = 0; i < size; i++) {
- final TargetDrawable targetIcon = targets.get(i);
- final float angle = alpha * i;
- targetIcon.setPositionX(centerX);
- targetIcon.setPositionY(centerY);
- targetIcon.setX(mOuterRadius * (float) Math.cos(angle));
- targetIcon.setY(mOuterRadius * (float) Math.sin(angle));
- }
- }
-
- private void updateChevronPositions(float centerX, float centerY) {
- ArrayList<TargetDrawable> chevrons = mChevronDrawables;
- final int size = chevrons.size();
- for (int i = 0; i < size; i++) {
- TargetDrawable target = chevrons.get(i);
- if (target != null) {
- target.setPositionX(centerX);
- target.setPositionY(centerY);
- }
- }
- }
-
- private void hideChevrons() {
- ArrayList<TargetDrawable> chevrons = mChevronDrawables;
- final int size = chevrons.size();
- for (int i = 0; i < size; i++) {
- TargetDrawable chevron = chevrons.get(i);
- if (chevron != null) {
- chevron.setAlpha(0.0f);
- }
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- mOuterRing.draw(canvas);
- final int ntargets = mTargetDrawables.size();
- for (int i = 0; i < ntargets; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- if (target != null) {
- target.draw(canvas);
- }
- }
- final int nchevrons = mChevronDrawables.size();
- for (int i = 0; i < nchevrons; i++) {
- TargetDrawable chevron = mChevronDrawables.get(i);
- if (chevron != null) {
- chevron.draw(canvas);
- }
- }
- mHandleDrawable.draw(canvas);
- }
-
- public void setOnTriggerListener(OnTriggerListener listener) {
- mOnTriggerListener = listener;
- }
-
- private float square(float d) {
- return d * d;
- }
-
- private float dist2(float dx, float dy) {
- return dx*dx + dy*dy;
- }
-
- private float getScaledTapRadiusSquared() {
- final float scaledTapRadius;
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- scaledTapRadius = TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mTapRadius;
- } else {
- scaledTapRadius = mTapRadius;
- }
- return square(scaledTapRadius);
- }
-
- private void announceTargets() {
- StringBuilder utterance = new StringBuilder();
- final int targetCount = mTargetDrawables.size();
- for (int i = 0; i < targetCount; i++) {
- String targetDescription = getTargetDescription(i);
- String directionDescription = getDirectionDescription(i);
- if (!TextUtils.isEmpty(targetDescription)
- && !TextUtils.isEmpty(directionDescription)) {
- String text = String.format(directionDescription, targetDescription);
- utterance.append(text);
- }
- if (utterance.length() > 0) {
- announceText(utterance.toString());
- }
- }
- }
-
- private void announceText(String text) {
- setContentDescription(text);
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- setContentDescription(null);
- }
-
- private String getTargetDescription(int index) {
- if (mTargetDescriptions == null || mTargetDescriptions.isEmpty()) {
- mTargetDescriptions = loadDescriptions(mTargetDescriptionsResourceId);
- if (mTargetDrawables.size() != mTargetDescriptions.size()) {
- Log.w(TAG, "The number of target drawables must be"
- + " euqal to the number of target descriptions.");
- return null;
- }
- }
- return mTargetDescriptions.get(index);
- }
-
- private String getDirectionDescription(int index) {
- if (mDirectionDescriptions == null || mDirectionDescriptions.isEmpty()) {
- mDirectionDescriptions = loadDescriptions(mDirectionDescriptionsResourceId);
- if (mTargetDrawables.size() != mDirectionDescriptions.size()) {
- Log.w(TAG, "The number of target drawables must be"
- + " euqal to the number of direction descriptions.");
- return null;
- }
- }
- return mDirectionDescriptions.get(index);
- }
-
- private ArrayList<String> loadDescriptions(int resourceId) {
- TypedArray array = getContext().getResources().obtainTypedArray(resourceId);
- final int count = array.length();
- ArrayList<String> targetContentDescriptions = new ArrayList<String>(count);
- for (int i = 0; i < count; i++) {
- String contentDescription = array.getString(i);
- targetContentDescriptions.add(contentDescription);
- }
- array.recycle();
- return targetContentDescriptions;
- }
-
- public int getResourceIdForTarget(int index) {
- final TargetDrawable drawable = mTargetDrawables.get(index);
- return drawable == null ? 0 : drawable.getResourceId();
- }
-
- public void setEnableTarget(int resourceId, boolean enabled) {
- for (int i = 0; i < mTargetDrawables.size(); i++) {
- final TargetDrawable target = mTargetDrawables.get(i);
- if (target.getResourceId() == resourceId) {
- target.setEnabled(enabled);
- break; // should never be more than one match
- }
- }
- }
-
- /**
- * Gets the position of a target in the array that matches the given resource.
- * @param resourceId
- * @return the index or -1 if not found
- */
- public int getTargetPosition(int resourceId) {
- for (int i = 0; i < mTargetDrawables.size(); i++) {
- final TargetDrawable target = mTargetDrawables.get(i);
- if (target.getResourceId() == resourceId) {
- return i; // should never be more than one match
- }
- }
- return -1;
- }
-
- private boolean replaceTargetDrawables(Resources res, int existingResourceId,
- int newResourceId) {
- if (existingResourceId == 0 || newResourceId == 0) {
- return false;
- }
-
- boolean result = false;
- final ArrayList<TargetDrawable> drawables = mTargetDrawables;
- final int size = drawables.size();
- for (int i = 0; i < size; i++) {
- final TargetDrawable target = drawables.get(i);
- if (target != null && target.getResourceId() == existingResourceId) {
- target.setDrawable(res, newResourceId);
- result = true;
- }
- }
-
- if (result) {
- requestLayout(); // in case any given drawable's size changes
- }
-
- return result;
- }
-
- /**
- * Searches the given package for a resource to use to replace the Drawable on the
- * target with the given resource id
- * @param component of the .apk that contains the resource
- * @param name of the metadata in the .apk
- * @param existingResId the resource id of the target to search for
- * @return true if found in the given package and replaced at least one target Drawables
- */
- public boolean replaceTargetDrawablesIfPresent(ComponentName component, String name,
- int existingResId) {
- if (existingResId == 0) return false;
-
- try {
- PackageManager packageManager = mContext.getPackageManager();
- // Look for the search icon specified in the activity meta-data
- Bundle metaData = packageManager.getActivityInfo(
- component, PackageManager.GET_META_DATA).metaData;
- if (metaData != null) {
- int iconResId = metaData.getInt(name);
- if (iconResId != 0) {
- Resources res = packageManager.getResourcesForActivity(component);
- return replaceTargetDrawables(res, existingResId, iconResId);
- }
- }
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Failed to swap drawable; "
- + component.flattenToShortString() + " not found", e);
- } catch (Resources.NotFoundException nfe) {
- Log.w(TAG, "Failed to swap drawable from "
- + component.flattenToShortString(), nfe);
- }
- return false;
- }
-}
diff --git a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
index 16bec16..5a4c441 100644
--- a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
+++ b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
@@ -18,7 +18,6 @@ package com.android.internal.widget.multiwaveview;
import android.content.res.Resources;
import android.graphics.Canvas;
-import android.graphics.ColorFilter;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.util.Log;
diff --git a/core/java/com/android/server/AppWidgetBackupBridge.java b/core/java/com/android/server/AppWidgetBackupBridge.java
new file mode 100644
index 0000000..2ea2f79
--- /dev/null
+++ b/core/java/com/android/server/AppWidgetBackupBridge.java
@@ -0,0 +1,63 @@
+/*
+ * 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.server;
+
+import java.util.List;
+
+/**
+ * Runtime bridge between the Backup Manager Service and the App Widget Service,
+ * since those two modules are intentionally decoupled for modularity.
+ *
+ * @hide
+ */
+public class AppWidgetBackupBridge {
+ private static WidgetBackupProvider sAppWidgetService;
+
+ public static void register(WidgetBackupProvider instance) {
+ sAppWidgetService = instance;
+ }
+
+ public static List<String> getWidgetParticipants(int userId) {
+ return (sAppWidgetService != null)
+ ? sAppWidgetService.getWidgetParticipants(userId)
+ : null;
+ }
+
+ public static byte[] getWidgetState(String packageName, int userId) {
+ return (sAppWidgetService != null)
+ ? sAppWidgetService.getWidgetState(packageName, userId)
+ : null;
+ }
+
+ public static void restoreStarting(int userId) {
+ if (sAppWidgetService != null) {
+ sAppWidgetService.restoreStarting(userId);
+ }
+ }
+
+ public static void restoreWidgetState(String packageName, byte[] restoredState, int userId) {
+ if (sAppWidgetService != null) {
+ sAppWidgetService.restoreWidgetState(packageName, restoredState, userId);
+ }
+ }
+
+ public static void restoreFinished(int userId) {
+ if (sAppWidgetService != null) {
+ sAppWidgetService.restoreFinished(userId);
+ }
+ }
+}
diff --git a/core/java/com/android/server/SystemService.java b/core/java/com/android/server/SystemService.java
index e374563..bf36bb1 100644
--- a/core/java/com/android/server/SystemService.java
+++ b/core/java/com/android/server/SystemService.java
@@ -123,6 +123,38 @@ public abstract class SystemService {
public void onBootPhase(int phase) {}
/**
+ * Called when a new user is starting, for system services to initialize any per-user
+ * state they maintain for running users.
+ * @param userHandle The identifier of the user.
+ */
+ public void onStartUser(int userHandle) {}
+
+ /**
+ * Called when switching to a different foreground user, for system services that have
+ * special behavior for whichever user is currently in the foreground. This is called
+ * before any application processes are aware of the new user.
+ * @param userHandle The identifier of the user.
+ */
+ public void onSwitchUser(int userHandle) {}
+
+ /**
+ * Called when an existing user is stopping, for system services to finalize any per-user
+ * state they maintain for running users. This is called prior to sending the SHUTDOWN
+ * broadcast to the user; it is a good place to stop making use of any resources of that
+ * user (such as binding to a service running in the user).
+ * @param userHandle The identifier of the user.
+ */
+ public void onStopUser(int userHandle) {}
+
+ /**
+ * Called when an existing user is stopping, for system services to finalize any per-user
+ * state they maintain for running users. This is called after all application process
+ * teardown of the user is complete.
+ * @param userHandle The identifier of the user.
+ */
+ public void onCleanupUser(int userHandle) {}
+
+ /**
* Publish the service so it is accessible to other services and apps.
*/
protected final void publishBinderService(String name, IBinder service) {
diff --git a/core/java/com/android/server/SystemServiceManager.java b/core/java/com/android/server/SystemServiceManager.java
index eb8df0e..87a50a9 100644
--- a/core/java/com/android/server/SystemServiceManager.java
+++ b/core/java/com/android/server/SystemServiceManager.java
@@ -131,6 +131,58 @@ public class SystemServiceManager {
}
}
+ public void startUser(final int userHandle) {
+ final int serviceLen = mServices.size();
+ for (int i = 0; i < serviceLen; i++) {
+ final SystemService service = mServices.get(i);
+ try {
+ service.onStartUser(userHandle);
+ } catch (Exception ex) {
+ Slog.wtf(TAG, "Failure reporting start of user " + userHandle
+ + " to service " + service.getClass().getName(), ex);
+ }
+ }
+ }
+
+ public void switchUser(final int userHandle) {
+ final int serviceLen = mServices.size();
+ for (int i = 0; i < serviceLen; i++) {
+ final SystemService service = mServices.get(i);
+ try {
+ service.onSwitchUser(userHandle);
+ } catch (Exception ex) {
+ Slog.wtf(TAG, "Failure reporting switch of user " + userHandle
+ + " to service " + service.getClass().getName(), ex);
+ }
+ }
+ }
+
+ public void stopUser(final int userHandle) {
+ final int serviceLen = mServices.size();
+ for (int i = 0; i < serviceLen; i++) {
+ final SystemService service = mServices.get(i);
+ try {
+ service.onStopUser(userHandle);
+ } catch (Exception ex) {
+ Slog.wtf(TAG, "Failure reporting stop of user " + userHandle
+ + " to service " + service.getClass().getName(), ex);
+ }
+ }
+ }
+
+ public void cleanupUser(final int userHandle) {
+ final int serviceLen = mServices.size();
+ for (int i = 0; i < serviceLen; i++) {
+ final SystemService service = mServices.get(i);
+ try {
+ service.onCleanupUser(userHandle);
+ } catch (Exception ex) {
+ Slog.wtf(TAG, "Failure reporting cleanup of user " + userHandle
+ + " to service " + service.getClass().getName(), ex);
+ }
+ }
+ }
+
/** Sets the safe mode flag for services to query. */
public void setSafeMode(boolean safeMode) {
mSafeMode = safeMode;
diff --git a/core/java/com/android/server/WidgetBackupProvider.java b/core/java/com/android/server/WidgetBackupProvider.java
new file mode 100644
index 0000000..a2efbdd
--- /dev/null
+++ b/core/java/com/android/server/WidgetBackupProvider.java
@@ -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 com.android.server;
+
+import java.util.List;
+
+/**
+ * Shim to allow core/backup to communicate with the app widget service
+ * about various important events without needing to be able to see the
+ * implementation of the service.
+ *
+ * @hide
+ */
+public interface WidgetBackupProvider {
+ public List<String> getWidgetParticipants(int userId);
+ public byte[] getWidgetState(String packageName, int userId);
+ public void restoreStarting(int userId);
+ public void restoreWidgetState(String packageName, byte[] restoredState, int userId);
+ public void restoreFinished(int userId);
+}
diff --git a/core/java/com/android/server/net/BaseNetworkObserver.java b/core/java/com/android/server/net/BaseNetworkObserver.java
index 5502a17..430dd63 100644
--- a/core/java/com/android/server/net/BaseNetworkObserver.java
+++ b/core/java/com/android/server/net/BaseNetworkObserver.java
@@ -57,7 +57,7 @@ public class BaseNetworkObserver extends INetworkManagementEventObserver.Stub {
}
@Override
- public void interfaceClassDataActivityChanged(String label, boolean active) {
+ public void interfaceClassDataActivityChanged(String label, boolean active, long tsNanos) {
// default no-op
}