summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/alsa/AlsaCardsParser.java2
-rw-r--r--core/java/android/alsa/AlsaDevicesParser.java2
-rw-r--r--core/java/android/alsa/LineTokenizer.java2
-rw-r--r--core/java/android/animation/BidirectionalTypeConverter.java73
-rw-r--r--core/java/android/animation/ObjectAnimator.java4
-rw-r--r--core/java/android/animation/PropertyValuesHolder.java10
-rw-r--r--core/java/android/animation/TypeConverter.java12
-rw-r--r--core/java/android/app/ActionBar.java23
-rw-r--r--core/java/android/app/Activity.java401
-rw-r--r--core/java/android/app/ActivityManager.aidl19
-rw-r--r--core/java/android/app/ActivityManager.java216
-rw-r--r--core/java/android/app/ActivityManagerNative.java169
-rw-r--r--core/java/android/app/ActivityOptions.java271
-rw-r--r--core/java/android/app/ActivityThread.java122
-rw-r--r--core/java/android/app/ActivityTransitionCoordinator.java858
-rw-r--r--core/java/android/app/ActivityTransitionState.java209
-rw-r--r--core/java/android/app/AlertDialog.java20
-rw-r--r--core/java/android/app/AppOpsManager.java65
-rw-r--r--core/java/android/app/ApplicationPackageManager.java44
-rw-r--r--core/java/android/app/ApplicationThreadNative.java25
-rw-r--r--core/java/android/app/ContextImpl.java114
-rw-r--r--core/java/android/app/DatePickerDialog.java1
-rw-r--r--core/java/android/app/EnterTransitionCoordinator.java469
-rw-r--r--core/java/android/app/ExitTransitionCoordinator.java349
-rw-r--r--core/java/android/app/FragmentBreadCrumbs.java6
-rw-r--r--core/java/android/app/IActivityManager.java42
-rw-r--r--core/java/android/app/IAppTask.aidl25
-rw-r--r--core/java/android/app/IApplicationThread.java11
-rw-r--r--core/java/android/app/INotificationManager.aidl6
-rw-r--r--core/java/android/app/IUiAutomationConnection.aidl1
-rw-r--r--core/java/android/app/Instrumentation.java99
-rw-r--r--core/java/android/app/KeyguardManager.java4
-rw-r--r--core/java/android/app/LauncherActivity.java12
-rw-r--r--core/java/android/app/Notification.java1337
-rw-r--r--core/java/android/app/PackageInstallObserver.java40
-rw-r--r--core/java/android/app/PackageUninstallObserver.java (renamed from core/java/android/tv/ITvInputService.aidl)30
-rw-r--r--core/java/android/app/RemoteInput.java311
-rw-r--r--core/java/android/app/SharedElementListener.java114
-rw-r--r--core/java/android/app/TaskManagerImpl.java62
-rw-r--r--core/java/android/app/TimePickerDialog.java1
-rw-r--r--core/java/android/app/UiAutomation.java41
-rw-r--r--core/java/android/app/UiAutomationConnection.java46
-rw-r--r--core/java/android/app/VoiceInteractor.java140
-rw-r--r--core/java/android/app/WallpaperManager.java29
-rw-r--r--core/java/android/app/admin/DeviceAdminReceiver.java9
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java281
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl19
-rw-r--r--core/java/android/app/backup/BackupTransport.java415
-rw-r--r--core/java/android/app/task/ITaskCallback.aidl7
-rw-r--r--core/java/android/app/task/ITaskManager.aidl30
-rw-r--r--core/java/android/app/task/Task.aidl (renamed from core/java/android/tv/TvInputInfo.aidl)7
-rw-r--r--core/java/android/app/task/Task.java (renamed from core/java/android/content/Task.java)133
-rw-r--r--core/java/android/app/task/TaskManager.java (renamed from core/java/android/content/TaskManager.java)22
-rw-r--r--core/java/android/app/task/TaskParams.java21
-rw-r--r--core/java/android/app/task/TaskService.java87
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java12
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java34
-rw-r--r--core/java/android/bluetooth/BluetoothGatt.java10
-rw-r--r--core/java/android/bluetooth/BluetoothProfile.java6
-rw-r--r--core/java/android/bluetooth/BluetoothSocket.java22
-rw-r--r--core/java/android/bluetooth/BluetoothTetheringDataTracker.java14
-rw-r--r--core/java/android/bluetooth/BluetoothUuid.java20
-rw-r--r--core/java/android/bluetooth/IBluetoothGatt.aidl14
-rw-r--r--core/java/android/bluetooth/IBluetoothGattCallback.aidl1
-rw-r--r--core/java/android/bluetooth/le/AdvertiseCallback.java68
-rw-r--r--core/java/android/bluetooth/le/AdvertiseSettings.aidl19
-rw-r--r--core/java/android/bluetooth/le/AdvertiseSettings.java218
-rw-r--r--core/java/android/bluetooth/le/AdvertisementData.aidl19
-rw-r--r--core/java/android/bluetooth/le/AdvertisementData.java344
-rw-r--r--core/java/android/bluetooth/le/BluetoothLeAdvertiser.java368
-rw-r--r--core/java/android/bluetooth/le/BluetoothLeScanner.java371
-rw-r--r--core/java/android/bluetooth/le/ScanCallback.java79
-rw-r--r--core/java/android/bluetooth/le/ScanFilter.aidl19
-rw-r--r--core/java/android/bluetooth/le/ScanFilter.java588
-rw-r--r--core/java/android/bluetooth/le/ScanRecord.java278
-rw-r--r--core/java/android/bluetooth/le/ScanResult.aidl19
-rw-r--r--core/java/android/bluetooth/le/ScanResult.java162
-rw-r--r--core/java/android/bluetooth/le/ScanSettings.aidl19
-rw-r--r--core/java/android/bluetooth/le/ScanSettings.java221
-rw-r--r--core/java/android/content/ClipData.java21
-rw-r--r--core/java/android/content/ContentProvider.java114
-rw-r--r--core/java/android/content/ContentProviderOperation.java27
-rw-r--r--core/java/android/content/ContentProviderResult.java10
-rw-r--r--core/java/android/content/ContentResolver.java11
-rw-r--r--core/java/android/content/Context.java118
-rw-r--r--core/java/android/content/ContextWrapper.java15
-rw-r--r--core/java/android/content/IRestrictionsManager.aidl30
-rw-r--r--core/java/android/content/Intent.java161
-rw-r--r--core/java/android/content/RestrictionEntry.java157
-rw-r--r--core/java/android/content/RestrictionsManager.java344
-rw-r--r--core/java/android/content/SharedPreferences.java11
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java8
-rw-r--r--core/java/android/content/pm/ContainerEncryptionParams.java4
-rw-r--r--core/java/android/content/pm/IPackageInstallObserver2.aidl33
-rw-r--r--core/java/android/content/pm/IPackageInstaller.aidl33
-rw-r--r--core/java/android/content/pm/IPackageInstallerSession.aidl30
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl18
-rw-r--r--core/java/android/content/pm/LauncherActivityInfo.java44
-rw-r--r--core/java/android/content/pm/LauncherApps.java147
-rw-r--r--core/java/android/content/pm/ManifestDigest.java2
-rw-r--r--core/java/android/content/pm/PackageInstaller.java169
-rw-r--r--core/java/android/content/pm/PackageInstallerParams.aidl19
-rw-r--r--core/java/android/content/pm/PackageInstallerParams.java141
-rw-r--r--core/java/android/content/pm/PackageManager.java92
-rw-r--r--core/java/android/content/pm/PackageParser.java197
-rw-r--r--core/java/android/content/pm/Signature.java4
-rw-r--r--core/java/android/content/pm/VerificationParams.java2
-rw-r--r--core/java/android/content/res/ColorStateList.java45
-rw-r--r--core/java/android/content/res/Resources.java109
-rw-r--r--core/java/android/content/res/TypedArray.java34
-rw-r--r--core/java/android/database/CursorWindow.java15
-rw-r--r--core/java/android/ddm/DdmHandleHello.java22
-rw-r--r--core/java/android/hardware/Camera.java49
-rw-r--r--core/java/android/hardware/Sensor.java435
-rw-r--r--core/java/android/hardware/SensorEventListener.java12
-rw-r--r--core/java/android/hardware/SensorManager.java12
-rw-r--r--core/java/android/hardware/SystemSensorManager.java25
-rw-r--r--core/java/android/hardware/camera2/CameraAccessException.java5
-rw-r--r--core/java/android/hardware/camera2/CameraCaptureSession.java669
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java641
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java273
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java80
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java185
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java427
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java612
-rw-r--r--core/java/android/hardware/camera2/DngCreator.java368
-rw-r--r--core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl2
-rw-r--r--core/java/android/hardware/camera2/ICameraDeviceUser.aidl26
-rw-r--r--core/java/android/hardware/camera2/Size.java102
-rw-r--r--core/java/android/hardware/camera2/StreamConfigurationMap.java508
-rw-r--r--core/java/android/hardware/camera2/TotalCaptureResult.java84
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDevice.java46
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java1018
-rw-r--r--core/java/android/hardware/camera2/impl/CaptureResultExtras.aidl (renamed from core/java/android/hardware/camera2/CaptureResultExtras.aidl)2
-rw-r--r--core/java/android/hardware/camera2/impl/CaptureResultExtras.java (renamed from core/java/android/hardware/camera2/CaptureResultExtras.java)11
-rw-r--r--core/java/android/hardware/camera2/impl/MetadataMarshalClass.java67
-rw-r--r--core/java/android/hardware/camera2/impl/MetadataMarshalRect.java67
-rw-r--r--core/java/android/hardware/camera2/impl/MetadataMarshalSize.java60
-rw-r--r--core/java/android/hardware/camera2/impl/MetadataMarshalString.java79
-rw-r--r--core/java/android/hardware/camera2/legacy/BurstHolder.java82
-rw-r--r--core/java/android/hardware/camera2/legacy/CameraDeviceState.java259
-rw-r--r--core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java273
-rw-r--r--core/java/android/hardware/camera2/legacy/GLThreadManager.java234
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java275
-rw-r--r--core/java/android/hardware/camera2/legacy/RequestHandlerThread.java101
-rw-r--r--core/java/android/hardware/camera2/legacy/RequestHolder.java159
-rw-r--r--core/java/android/hardware/camera2/legacy/RequestQueue.java132
-rw-r--r--core/java/android/hardware/camera2/legacy/RequestThreadManager.java491
-rw-r--r--core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java522
-rw-r--r--core/java/android/hardware/camera2/legacy/package.html3
-rw-r--r--core/java/android/hardware/camera2/marshal/MarshalHelpers.java243
-rw-r--r--core/java/android/hardware/camera2/marshal/MarshalQueryable.java63
-rw-r--r--core/java/android/hardware/camera2/marshal/MarshalRegistry.java133
-rw-r--r--core/java/android/hardware/camera2/marshal/Marshaler.java148
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java182
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableBoolean.java67
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableColorSpaceTransform.java84
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java220
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableMeteringRectangle.java88
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableNativeByteToInteger.java70
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePair.java158
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java193
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePrimitive.java185
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRange.java139
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRect.java77
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableReprocessFormatsMap.java131
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRggbChannelVector.java75
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableSize.java68
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableSizeF.java72
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableStreamConfiguration.java80
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableStreamConfigurationDuration.java90
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableString.java110
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/package.html3
-rw-r--r--core/java/android/hardware/camera2/marshal/package.html3
-rw-r--r--core/java/android/hardware/camera2/params/ColorSpaceTransform.java (renamed from core/java/android/hardware/camera2/ColorSpaceTransform.java)15
-rw-r--r--core/java/android/hardware/camera2/params/Face.java (renamed from core/java/android/hardware/camera2/Face.java)5
-rw-r--r--core/java/android/hardware/camera2/params/LensShadingMap.java (renamed from core/java/android/hardware/camera2/LensShadingMap.java)16
-rw-r--r--core/java/android/hardware/camera2/params/MeteringRectangle.java (renamed from core/java/android/hardware/camera2/MeteringRectangle.java)75
-rw-r--r--core/java/android/hardware/camera2/params/ReprocessFormatsMap.java (renamed from core/java/android/hardware/camera2/ReprocessFormatsMap.java)26
-rw-r--r--core/java/android/hardware/camera2/params/RggbChannelVector.java (renamed from core/java/android/hardware/camera2/RggbChannelVector.java)2
-rw-r--r--core/java/android/hardware/camera2/params/StreamConfiguration.java (renamed from core/java/android/hardware/camera2/StreamConfiguration.java)14
-rw-r--r--core/java/android/hardware/camera2/params/StreamConfigurationDuration.java (renamed from core/java/android/hardware/camera2/StreamConfigurationDuration.java)13
-rw-r--r--core/java/android/hardware/camera2/params/StreamConfigurationMap.java949
-rw-r--r--core/java/android/hardware/camera2/params/TonemapCurve.java (renamed from core/java/android/hardware/camera2/TonemapCurve.java)8
-rw-r--r--core/java/android/hardware/camera2/utils/CameraBinderDecorator.java5
-rw-r--r--core/java/android/hardware/camera2/utils/HashCodeHelpers.java (renamed from core/java/android/hardware/camera2/impl/HashCodeHelpers.java)2
-rw-r--r--core/java/android/hardware/camera2/utils/LongParcelable.aidl (renamed from core/java/android/hardware/camera2/LongParcelable.aidl)4
-rw-r--r--core/java/android/hardware/camera2/utils/LongParcelable.java (renamed from core/java/android/hardware/camera2/LongParcelable.java)2
-rw-r--r--core/java/android/hardware/camera2/utils/TypeReference.java435
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java10
-rw-r--r--core/java/android/hardware/hdmi/HdmiCec.java23
-rw-r--r--core/java/android/hardware/hdmi/HdmiCecClient.java38
-rw-r--r--core/java/android/hardware/hdmi/HdmiCecManager.java10
-rw-r--r--core/java/android/hardware/hdmi/HdmiCecMessage.java39
-rw-r--r--core/java/android/hardware/hdmi/HdmiControlManager.java157
-rw-r--r--core/java/android/hardware/hdmi/HdmiHotplugEvent.aidl19
-rw-r--r--core/java/android/hardware/hdmi/HdmiHotplugEvent.java100
-rw-r--r--core/java/android/hardware/hdmi/HdmiPlaybackClient.java116
-rw-r--r--core/java/android/hardware/hdmi/HdmiTvClient.java31
-rw-r--r--core/java/android/hardware/hdmi/IHdmiControlCallback.aidl (renamed from core/java/android/tv/ITvInputSessionCallback.aidl)13
-rw-r--r--core/java/android/hardware/hdmi/IHdmiControlService.aidl36
-rw-r--r--core/java/android/hardware/hdmi/IHdmiHotplugEventListener.aidl (renamed from core/java/android/tv/ITvInputServiceCallback.aidl)13
-rw-r--r--core/java/android/hardware/usb/UsbConfiguration.java30
-rw-r--r--core/java/android/hardware/usb/UsbDeviceConnection.java2
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java7
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java3
-rw-r--r--core/java/android/inputmethodservice/SoftInputWindow.java78
-rw-r--r--core/java/android/net/BaseNetworkStateTracker.java19
-rw-r--r--core/java/android/net/ConnectivityManager.java857
-rw-r--r--core/java/android/net/DummyDataStateTracker.java7
-rw-r--r--core/java/android/net/EthernetDataTracker.java437
-rw-r--r--core/java/android/net/EthernetManager.java72
-rw-r--r--core/java/android/net/IConnectivityManager.aidl48
-rw-r--r--core/java/android/net/IEthernetManager.aidl30
-rw-r--r--core/java/android/net/IpConfiguration.aidl19
-rw-r--r--core/java/android/net/IpConfiguration.java151
-rw-r--r--core/java/android/net/LinkAddress.java32
-rw-r--r--core/java/android/net/LinkCapabilities.java362
-rw-r--r--core/java/android/net/LinkProperties.java197
-rw-r--r--core/java/android/net/LinkSocket.java276
-rw-r--r--core/java/android/net/LinkSocketNotifier.java86
-rw-r--r--core/java/android/net/LocalSocketImpl.java5
-rw-r--r--core/java/android/net/MobileDataStateTracker.java25
-rw-r--r--core/java/android/net/Network.aidl (renamed from core/java/android/net/LinkCapabilities.aidl)5
-rw-r--r--core/java/android/net/Network.java250
-rw-r--r--core/java/android/net/NetworkAgent.java202
-rw-r--r--core/java/android/net/NetworkCapabilities.aidl21
-rw-r--r--core/java/android/net/NetworkCapabilities.java510
-rw-r--r--core/java/android/net/NetworkFactory.java275
-rw-r--r--core/java/android/net/NetworkInfo.java19
-rw-r--r--core/java/android/net/NetworkRequest.aidl20
-rw-r--r--core/java/android/net/NetworkRequest.java116
-rw-r--r--core/java/android/net/NetworkState.java14
-rw-r--r--core/java/android/net/NetworkStateTracker.java15
-rw-r--r--core/java/android/net/NetworkUtils.java44
-rw-r--r--core/java/android/net/Proxy.java8
-rw-r--r--core/java/android/net/ProxyDataTracker.java2
-rw-r--r--core/java/android/net/ProxyInfo.java53
-rw-r--r--core/java/android/net/RouteInfo.java251
-rw-r--r--core/java/android/nfc/INfcAdapter.aidl2
-rw-r--r--core/java/android/nfc/INfcUnlockSettings.aidl70
-rw-r--r--core/java/android/nfc/NfcAdapter.java24
-rw-r--r--core/java/android/nfc/NfcUnlock.java255
-rw-r--r--core/java/android/nfc/cardemulation/AidGroup.java17
-rw-r--r--core/java/android/nfc/cardemulation/CardEmulation.java51
-rw-r--r--core/java/android/os/BaseBundle.java (renamed from core/java/android/os/CommonBundle.java)125
-rw-r--r--core/java/android/os/BatteryManager.java4
-rw-r--r--core/java/android/os/BatteryProperty.java30
-rw-r--r--core/java/android/os/BatteryStats.java319
-rw-r--r--core/java/android/os/Build.java22
-rw-r--r--core/java/android/os/Bundle.java363
-rw-r--r--core/java/android/os/Environment.java4
-rw-r--r--core/java/android/os/FileBridge.java165
-rw-r--r--core/java/android/os/FileUtils.java29
-rw-r--r--core/java/android/os/INetworkManagementService.aidl90
-rw-r--r--core/java/android/os/IPowerManager.aidl2
-rw-r--r--core/java/android/os/PersistableBundle.java509
-rw-r--r--core/java/android/os/PowerManager.java24
-rw-r--r--core/java/android/os/PowerManagerInternal.java8
-rw-r--r--core/java/android/os/Process.java155
-rw-r--r--core/java/android/os/RemoteException.java5
-rw-r--r--core/java/android/os/Trace.java2
-rw-r--r--core/java/android/os/UserManager.java11
-rw-r--r--core/java/android/os/storage/StorageManager.java28
-rw-r--r--core/java/android/preference/PreferenceManager.java6
-rw-r--r--core/java/android/preference/SeekBarVolumizer.java318
-rw-r--r--core/java/android/preference/VolumePreference.java233
-rw-r--r--core/java/android/provider/ContactsContract.java27
-rw-r--r--core/java/android/provider/DocumentsContract.java51
-rw-r--r--core/java/android/provider/DocumentsProvider.java97
-rw-r--r--core/java/android/provider/Settings.java156
-rw-r--r--core/java/android/provider/TvContract.java596
-rw-r--r--core/java/android/service/fingerprint/FingerprintManager.java17
-rw-r--r--core/java/android/service/notification/INotificationListener.aidl10
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java275
-rw-r--r--core/java/android/service/notification/NotificationRankingUpdate.aidl19
-rw-r--r--core/java/android/service/notification/NotificationRankingUpdate.java77
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java33
-rw-r--r--core/java/android/service/trust/ITrustAgentServiceCallback.aidl2
-rw-r--r--core/java/android/service/trust/TrustAgentService.java37
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java262
-rw-r--r--core/java/android/speech/tts/Markup.java537
-rw-r--r--core/java/android/speech/tts/SynthesisRequestV2.java38
-rw-r--r--core/java/android/speech/tts/TextToSpeechClient.java192
-rw-r--r--core/java/android/speech/tts/TextToSpeechService.java56
-rw-r--r--core/java/android/speech/tts/TtsEngines.java18
-rw-r--r--core/java/android/speech/tts/Utterance.java595
-rw-r--r--core/java/android/text/method/QwertyKeyListener.java2
-rw-r--r--core/java/android/transition/ChangeTransform.java2
-rw-r--r--core/java/android/transition/MoveImage.java8
-rw-r--r--core/java/android/transition/Transition.java712
-rw-r--r--core/java/android/transition/TransitionInflater.java46
-rw-r--r--core/java/android/transition/TransitionSet.java30
-rw-r--r--core/java/android/transition/TransitionValuesMaps.java6
-rw-r--r--core/java/android/transition/Visibility.java29
-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/ITvInputSession.aidl39
-rw-r--r--core/java/android/tv/ITvInputSessionWrapper.java179
-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/Range.java2
-rw-r--r--core/java/android/util/Rational.java (renamed from core/java/android/hardware/camera2/Rational.java)29
-rw-r--r--core/java/android/util/TimeUtils.java3
-rw-r--r--core/java/android/view/Choreographer.java10
-rw-r--r--core/java/android/view/GLES20Canvas.java473
-rw-r--r--core/java/android/view/GLES20RecordingCanvas.java2
-rw-r--r--core/java/android/view/GLRenderer.java1537
-rw-r--r--core/java/android/view/HardwareCanvas.java42
-rw-r--r--core/java/android/view/HardwareLayer.java140
-rw-r--r--core/java/android/view/HardwareRenderer.java161
-rw-r--r--core/java/android/view/IWindowManager.aidl3
-rw-r--r--core/java/android/view/InputDevice.java8
-rw-r--r--core/java/android/view/InputEventConsistencyVerifier.java2
-rw-r--r--core/java/android/view/KeyEvent.java33
-rw-r--r--core/java/android/view/MotionEvent.java8
-rw-r--r--core/java/android/view/RenderNode.java17
-rw-r--r--core/java/android/view/RenderNodeAnimator.java242
-rw-r--r--core/java/android/view/SurfaceControl.java34
-rw-r--r--core/java/android/view/TextureView.java4
-rw-r--r--core/java/android/view/ThreadedRenderer.java149
-rw-r--r--core/java/android/view/View.java261
-rw-r--r--core/java/android/view/ViewGroup.java143
-rw-r--r--core/java/android/view/ViewPropertyAnimator.java39
-rw-r--r--core/java/android/view/ViewPropertyAnimatorRT.java118
-rw-r--r--core/java/android/view/ViewRootImpl.java137
-rw-r--r--core/java/android/view/VolumePanel.java1076
-rw-r--r--core/java/android/view/Window.java87
-rw-r--r--core/java/android/view/WindowManager.java22
-rw-r--r--core/java/android/view/WindowManagerGlobal.java7
-rw-r--r--core/java/android/view/WindowManagerPolicy.java26
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java629
-rw-r--r--core/java/android/view/animation/AccelerateDecelerateInterpolator.java13
-rw-r--r--core/java/android/view/animation/AccelerateInterpolator.java13
-rw-r--r--core/java/android/view/animation/AnticipateInterpolator.java13
-rw-r--r--core/java/android/view/animation/AnticipateOvershootInterpolator.java14
-rw-r--r--core/java/android/view/animation/BounceInterpolator.java13
-rw-r--r--core/java/android/view/animation/CycleInterpolator.java13
-rw-r--r--core/java/android/view/animation/DecelerateInterpolator.java13
-rw-r--r--core/java/android/view/animation/LinearInterpolator.java13
-rw-r--r--core/java/android/view/animation/OvershootInterpolator.java13
-rw-r--r--core/java/android/view/inputmethod/CursorAnchorInfo.java103
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java26
-rw-r--r--core/java/android/view/textservice/SpellCheckerSession.java8
-rw-r--r--core/java/android/webkit/BrowserDownloadListener.java57
-rw-r--r--core/java/android/webkit/EventLogTags.logtags1
-rw-r--r--core/java/android/webkit/PermissionRequest.java1
-rw-r--r--core/java/android/webkit/WebBackForwardListClient.java40
-rw-r--r--core/java/android/webkit/WebSettings.java32
-rw-r--r--core/java/android/webkit/WebViewFactory.java32
-rw-r--r--core/java/android/widget/AbsListView.java148
-rw-r--r--core/java/android/widget/AbsSeekBar.java249
-rw-r--r--core/java/android/widget/ActionMenuPresenter.java1
-rw-r--r--core/java/android/widget/ActionMenuView.java60
-rw-r--r--core/java/android/widget/CheckedTextView.java34
-rw-r--r--core/java/android/widget/CompoundButton.java2
-rw-r--r--core/java/android/widget/DatePicker.java4
-rw-r--r--core/java/android/widget/EdgeEffect.java250
-rw-r--r--core/java/android/widget/Editor.java130
-rw-r--r--core/java/android/widget/GridLayout.java228
-rw-r--r--core/java/android/widget/HorizontalScrollView.java6
-rw-r--r--core/java/android/widget/ImageView.java2
-rw-r--r--core/java/android/widget/ProgressBar.java26
-rw-r--r--core/java/android/widget/RtlSpacingHelper.java91
-rw-r--r--core/java/android/widget/ScrollView.java6
-rw-r--r--core/java/android/widget/SuggestionsAdapter.java2
-rw-r--r--core/java/android/widget/Switch.java129
-rw-r--r--core/java/android/widget/TextView.java2
-rw-r--r--core/java/android/widget/Toolbar.java874
-rw-r--r--core/java/android/widget/VideoView.java2
-rw-r--r--core/java/com/android/internal/app/AlertController.java119
-rw-r--r--core/java/com/android/internal/app/IAppOpsService.aidl7
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl9
-rw-r--r--core/java/com/android/internal/app/IMediaContainerService.aidl10
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractor.aidl4
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractorCallback.aidl1
-rw-r--r--core/java/com/android/internal/app/IntentForwarderActivity.java5
-rw-r--r--core/java/com/android/internal/app/ProcessStats.java9
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java3
-rw-r--r--core/java/com/android/internal/app/ToolbarActionBar.java179
-rw-r--r--core/java/com/android/internal/app/WindowDecorActionBar.java148
-rw-r--r--core/java/com/android/internal/backup/BackupConstants.java28
-rw-r--r--core/java/com/android/internal/backup/IBackupTransport.aidl2
-rw-r--r--core/java/com/android/internal/backup/LocalTransport.java25
-rw-r--r--core/java/com/android/internal/backup/LocalTransportService.java2
-rw-r--r--core/java/com/android/internal/content/NativeLibraryHelper.java15
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java361
-rw-r--r--core/java/com/android/internal/notification/DemoContactNotificationScorer.java188
-rw-r--r--core/java/com/android/internal/notification/NotificationScorer.java27
-rw-r--r--core/java/com/android/internal/notification/PeopleNotificationScorer.java227
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java222
-rw-r--r--core/java/com/android/internal/os/HandlerCaller.java4
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java39
-rw-r--r--core/java/com/android/internal/policy/IKeyguardService.aidl9
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl11
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl9
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java68
-rw-r--r--core/java/com/android/internal/util/AsyncChannel.java3
-rw-r--r--core/java/com/android/internal/util/Preconditions.java27
-rw-r--r--core/java/com/android/internal/util/Protocol.java6
-rw-r--r--core/java/com/android/internal/util/VirtualRefBasePtr.java53
-rw-r--r--core/java/com/android/internal/util/XmlUtils.java559
-rw-r--r--core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java75
-rw-r--r--core/java/com/android/internal/view/animation/HasNativeInterpolator.java37
-rw-r--r--core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java21
-rw-r--r--core/java/com/android/internal/view/animation/NativeInterpolatorFactoryHelper.java36
-rw-r--r--core/java/com/android/internal/widget/AbsActionBarView.java12
-rw-r--r--core/java/com/android/internal/widget/ActionBarContainer.java15
-rw-r--r--core/java/com/android/internal/widget/ActionBarContextView.java6
-rw-r--r--core/java/com/android/internal/widget/ActionBarOverlayLayout.java206
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java78
-rw-r--r--core/java/com/android/internal/widget/DecorContentParent.java53
-rw-r--r--core/java/com/android/internal/widget/DecorToolbar.java94
-rw-r--r--core/java/com/android/internal/widget/ILockSettings.aidl4
-rw-r--r--core/java/com/android/internal/widget/ILockSettingsObserver.aidl22
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java21
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtilsCache.java243
-rw-r--r--core/java/com/android/internal/widget/LockPatternView.java42
-rw-r--r--core/java/com/android/internal/widget/SwipeDismissLayout.java2
-rw-r--r--core/java/com/android/internal/widget/ToolbarWidgetWrapper.java541
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/GlowPadView.java8
423 files changed, 34714 insertions, 15975 deletions
diff --git a/core/java/android/alsa/AlsaCardsParser.java b/core/java/android/alsa/AlsaCardsParser.java
index f9af979..8b44881 100644
--- a/core/java/android/alsa/AlsaCardsParser.java
+++ b/core/java/android/alsa/AlsaCardsParser.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.alsascan;
+package android.alsa;
import android.util.Slog;
import java.io.BufferedReader;
diff --git a/core/java/android/alsa/AlsaDevicesParser.java b/core/java/android/alsa/AlsaDevicesParser.java
index 094c8a2..82cc1ae 100644
--- a/core/java/android/alsa/AlsaDevicesParser.java
+++ b/core/java/android/alsa/AlsaDevicesParser.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.alsascan;
+package android.alsa;
import android.util.Slog;
import java.io.BufferedReader;
diff --git a/core/java/android/alsa/LineTokenizer.java b/core/java/android/alsa/LineTokenizer.java
index c138fc5..78c91b5 100644
--- a/core/java/android/alsa/LineTokenizer.java
+++ b/core/java/android/alsa/LineTokenizer.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.alsascan;
+package android.alsa;
/**
* @hide
diff --git a/core/java/android/animation/BidirectionalTypeConverter.java b/core/java/android/animation/BidirectionalTypeConverter.java
new file mode 100644
index 0000000..960650e
--- /dev/null
+++ b/core/java/android/animation/BidirectionalTypeConverter.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+/**
+ * Abstract base class used convert type T to another type V and back again. This
+ * is necessary when the value types of in animation are different from the property
+ * type. BidirectionalTypeConverter is needed when only the final value for the
+ * animation is supplied to animators.
+ * @see PropertyValuesHolder#setConverter(TypeConverter)
+ */
+public abstract class BidirectionalTypeConverter<T, V> extends TypeConverter<T, V> {
+ private BidirectionalTypeConverter mInvertedConverter;
+
+ public BidirectionalTypeConverter(Class<T> fromClass, Class<V> toClass) {
+ super(fromClass, toClass);
+ }
+
+ /**
+ * 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.
+ * @param value The Object to convert.
+ * @return A value of type T, converted from <code>value</code>.
+ */
+ public abstract T convertBack(V value);
+
+ /**
+ * Returns the inverse of this converter, where the from and to classes are reversed.
+ * The inverted converter uses this convert to call {@link #convertBack(Object)} for
+ * {@link #convert(Object)} calls and {@link #convert(Object)} for
+ * {@link #convertBack(Object)} calls.
+ * @return The inverse of this converter, where the from and to classes are reversed.
+ */
+ public BidirectionalTypeConverter<V, T> invert() {
+ if (mInvertedConverter == null) {
+ mInvertedConverter = new InvertedConverter(this);
+ }
+ return mInvertedConverter;
+ }
+
+ private static class InvertedConverter<From, To> extends BidirectionalTypeConverter<From, To> {
+ private BidirectionalTypeConverter<To, From> mConverter;
+
+ public InvertedConverter(BidirectionalTypeConverter<To, From> converter) {
+ super(converter.getTargetType(), converter.getSourceType());
+ mConverter = converter;
+ }
+
+ @Override
+ public From convertBack(To value) {
+ return mConverter.convert(value);
+ }
+
+ @Override
+ public To convert(From value) {
+ return mConverter.convertBack(value);
+ }
+ }
+}
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index c0ce795..130754e 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -610,8 +610,8 @@ public final class ObjectAnimator extends ValueAnimator {
* 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.
+ * supplied, the <code>TypeConverter</code> must be a
+ * {@link android.animation.BidirectionalTypeConverter} to retrieve the current value.
*
* @param target The object whose property is to be animated.
* @param property The property being animated.
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index 8fce80a..bf2924c 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -456,7 +456,7 @@ public class PropertyValuesHolder implements Cloneable {
* 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
+ * must be a {@link android.animation.BidirectionalTypeConverter} to retrieve the current
* value.
*
* @param property The property being animated. Should not be null.
@@ -635,6 +635,8 @@ public class PropertyValuesHolder implements Cloneable {
/**
* Sets the converter to convert from the values type to the setter's parameter type.
+ * If only one value is supplied, <var>converter</var> must be a
+ * {@link android.animation.BidirectionalTypeConverter}.
* @param converter The converter to use to convert values.
*/
public void setConverter(TypeConverter converter) {
@@ -816,12 +818,12 @@ public class PropertyValuesHolder implements Cloneable {
private Object convertBack(Object value) {
if (mConverter != null) {
- value = mConverter.convertBack(value);
- if (value == null) {
+ if (!(mConverter instanceof BidirectionalTypeConverter)) {
throw new IllegalArgumentException("Converter "
+ mConverter.getClass().getName()
- + " must implement convertBack and not return null.");
+ + " must be a BidirectionalTypeConverter");
}
+ value = ((BidirectionalTypeConverter) mConverter).convertBack(value);
}
return value;
}
diff --git a/core/java/android/animation/TypeConverter.java b/core/java/android/animation/TypeConverter.java
index 03b3eb5..9ead2ad 100644
--- a/core/java/android/animation/TypeConverter.java
+++ b/core/java/android/animation/TypeConverter.java
@@ -53,16 +53,4 @@ public abstract class TypeConverter<T, V> {
* @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/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 3c3df01..d4c4318 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -26,6 +26,7 @@ import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.Gravity;
+import android.view.KeyEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
@@ -1013,6 +1014,26 @@ public abstract class ActionBar {
return null;
}
+ /** @hide */
+ public boolean openOptionsMenu() {
+ return false;
+ }
+
+ /** @hide */
+ public boolean invalidateOptionsMenu() {
+ return false;
+ }
+
+ /** @hide */
+ public boolean onMenuKeyEvent(KeyEvent event) {
+ return false;
+ }
+
+ /** @hide */
+ public boolean collapseActionView() {
+ return false;
+ }
+
/**
* Listener interface for ActionBar navigation events.
*
@@ -1291,6 +1312,7 @@ 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) {
@@ -1305,6 +1327,7 @@ public abstract class ActionBar {
public LayoutParams(LayoutParams source) {
super(source);
+ this.gravity = source.gravity;
}
public LayoutParams(ViewGroup.LayoutParams source) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ef6fcb7..23b5f29 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,6 +17,7 @@
package android.app;
import android.annotation.NonNull;
+import android.os.PersistableBundle;
import android.transition.Scene;
import android.transition.TransitionManager;
import android.util.ArrayMap;
@@ -29,7 +30,6 @@ import com.android.internal.policy.PolicyManager;
import android.annotation.IntDef;
import android.annotation.Nullable;
-import android.app.admin.DevicePolicyManager;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -716,6 +716,7 @@ public class Activity extends ContextThemeWrapper
HashMap<String, Object> children;
ArrayList<Fragment> fragments;
ArrayMap<String, LoaderManagerImpl> loaders;
+ VoiceInteractor voiceInteractor;
}
/* package */ NonConfigurationInstances mLastNonConfigurationInstances;
@@ -777,8 +778,10 @@ public class Activity extends ContextThemeWrapper
private Thread mUiThread;
final Handler mHandler = new Handler();
- private ActivityOptions mCalledActivityOptions;
- private EnterTransitionCoordinator mEnterTransitionCoordinator;
+
+ private ActivityTransitionState mActivityTransitionState = new ActivityTransitionState();
+ SharedElementListener mEnterTransitionListener = SharedElementListener.NULL_LISTENER;
+ SharedElementListener mExitTransitionListener = SharedElementListener.NULL_LISTENER;
/** Return the intent that started this activity. */
public Intent getIntent() {
@@ -918,10 +921,37 @@ public class Activity extends ContextThemeWrapper
}
mFragments.dispatchCreate();
getApplication().dispatchActivityCreated(this, savedInstanceState);
+ if (mVoiceInteractor != null) {
+ mVoiceInteractor.attachActivity(this);
+ }
mCalled = true;
}
/**
+ * Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with
+ * the attribute {@link android.R.attr#persistable} set true.
+ *
+ * @param savedInstanceState if the activity is being re-initialized after
+ * previously being shut down then this Bundle contains the data it most
+ * recently supplied in {@link #onSaveInstanceState}.
+ * <b><i>Note: Otherwise it is null.</i></b>
+ * @param persistentState if the activity is being re-initialized after
+ * previously being shut down or powered off then this Bundle contains the data it most
+ * recently supplied to outPersistentState in {@link #onSaveInstanceState}.
+ * <b><i>Note: Otherwise it is null.</i></b>
+ *
+ * @see #onCreate(android.os.Bundle)
+ * @see #onStart
+ * @see #onSaveInstanceState
+ * @see #onRestoreInstanceState
+ * @see #onPostCreate
+ */
+ protected void onCreate(@Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
+ onCreate(savedInstanceState);
+ }
+
+ /**
* The hook for {@link ActivityThread} to restore the state of this activity.
*
* Calls {@link #onSaveInstanceState(android.os.Bundle)} and
@@ -935,6 +965,23 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * The hook for {@link ActivityThread} to restore the state of this activity.
+ *
+ * Calls {@link #onSaveInstanceState(android.os.Bundle)} and
+ * {@link #restoreManagedDialogs(android.os.Bundle)}.
+ *
+ * @param savedInstanceState contains the saved state
+ * @param persistentState contains the persistable saved state
+ */
+ final void performRestoreInstanceState(Bundle savedInstanceState,
+ PersistableBundle persistentState) {
+ onRestoreInstanceState(savedInstanceState, persistentState);
+ if (savedInstanceState != null) {
+ restoreManagedDialogs(savedInstanceState);
+ }
+ }
+
+ /**
* This method is called after {@link #onStart} when the activity is
* being re-initialized from a previously saved state, given here in
* <var>savedInstanceState</var>. Most implementations will simply use {@link #onCreate}
@@ -962,7 +1009,34 @@ public class Activity extends ContextThemeWrapper
}
}
}
-
+
+ /**
+ * This is the same as {@link #onRestoreInstanceState(Bundle)} but is called for activities
+ * created with the attribute {@link android.R.attr#persistable}. The {@link
+ * android.os.PersistableBundle} passed came from the restored PersistableBundle first
+ * saved in {@link #onSaveInstanceState(Bundle, PersistableBundle)}.
+ *
+ * <p>This method is called between {@link #onStart} and
+ * {@link #onPostCreate}.
+ *
+ * <p>If this method is called {@link #onRestoreInstanceState(Bundle)} will not be called.
+ *
+ * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}.
+ * @param persistentState the data most recently supplied in {@link #onSaveInstanceState}.
+ *
+ * @see #onRestoreInstanceState(Bundle)
+ * @see #onCreate
+ * @see #onPostCreate
+ * @see #onResume
+ * @see #onSaveInstanceState
+ */
+ protected void onRestoreInstanceState(Bundle savedInstanceState,
+ PersistableBundle persistentState) {
+ if (savedInstanceState != null) {
+ onRestoreInstanceState(savedInstanceState);
+ }
+ }
+
/**
* Restore the state of any saved managed dialogs.
*
@@ -1032,13 +1106,25 @@ public class Activity extends ContextThemeWrapper
mTitleReady = true;
onTitleChanged(getTitle(), getTitleColor());
}
- if (mEnterTransitionCoordinator != null) {
- mEnterTransitionCoordinator.readyToEnter();
- }
mCalled = true;
}
/**
+ * This is the same as {@link #onPostCreate(Bundle)} but is called for activities
+ * created with the attribute {@link android.R.attr#persistable}.
+ *
+ * @param savedInstanceState The data most recently supplied in {@link #onSaveInstanceState}
+ * @param persistentState The data caming from the PersistableBundle first
+ * saved in {@link #onSaveInstanceState(Bundle, PersistableBundle)}.
+ *
+ * @see #onCreate
+ */
+ protected void onPostCreate(@Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
+ onPostCreate(savedInstanceState);
+ }
+
+ /**
* 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}.
@@ -1115,7 +1201,7 @@ public class Activity extends ContextThemeWrapper
protected void onResume() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
getApplication().dispatchActivityResumed(this);
- mCalledActivityOptions = null;
+ mActivityTransitionState.onResume();
mCalled = true;
}
@@ -1190,10 +1276,27 @@ public class Activity extends ContextThemeWrapper
final void performSaveInstanceState(Bundle outState) {
onSaveInstanceState(outState);
saveManagedDialogs(outState);
+ mActivityTransitionState.saveState(outState);
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
}
/**
+ * The hook for {@link ActivityThread} to save the state of this activity.
+ *
+ * Calls {@link #onSaveInstanceState(android.os.Bundle)}
+ * and {@link #saveManagedDialogs(android.os.Bundle)}.
+ *
+ * @param outState The bundle to save the state to.
+ * @param outPersistentState The bundle to save persistent state to.
+ */
+ final void performSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
+ onSaveInstanceState(outState, outPersistentState);
+ saveManagedDialogs(outState);
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState +
+ ", " + outPersistentState);
+ }
+
+ /**
* Called to retrieve per-instance state from an activity before being killed
* so that the state can be restored in {@link #onCreate} or
* {@link #onRestoreInstanceState} (the {@link Bundle} populated by this method
@@ -1248,6 +1351,25 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * This is the same as {@link #onSaveInstanceState} but is called for activities
+ * created with the attribute {@link android.R.attr#persistable}. The {@link
+ * android.os.PersistableBundle} passed in will be saved and presented in
+ * {@link #onCreate(Bundle, PersistableBundle)} the first time that this activity
+ * is restarted following the next device reboot.
+ *
+ * @param outState Bundle in which to place your saved state.
+ * @param outPersistentState State which will be saved across reboots.
+ *
+ * @see #onSaveInstanceState(Bundle)
+ * @see #onCreate
+ * @see #onRestoreInstanceState(Bundle, PersistableBundle)
+ * @see #onPause
+ */
+ protected void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
+ onSaveInstanceState(outState);
+ }
+
+ /**
* Save the state of any managed dialogs.
*
* @param outState place to store the saved state.
@@ -1425,10 +1547,7 @@ 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;
- }
+ mActivityTransitionState.onStop();
getApplication().dispatchActivityStopped(this);
mTranslucentCallback = null;
mCalled = true;
@@ -1715,7 +1834,8 @@ public class Activity extends ContextThemeWrapper
}
}
}
- if (activity == null && children == null && fragments == null && !retainLoaders) {
+ if (activity == null && children == null && fragments == null && !retainLoaders
+ && mVoiceInteractor == null) {
return null;
}
@@ -1724,6 +1844,7 @@ public class Activity extends ContextThemeWrapper
nci.children = children;
nci.fragments = fragments;
nci.loaders = mAllLoaderManagers;
+ nci.voiceInteractor = mVoiceInteractor;
return nci;
}
@@ -1954,15 +2075,16 @@ public class Activity extends ContextThemeWrapper
* <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
+ * @param toolbar Toolbar to set as the Activity's action bar
*/
- public void setActionBar(@Nullable Toolbar actionBar) {
+ public void setActionBar(@Nullable Toolbar toolbar) {
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);
+ mActionBar = new ToolbarActionBar(toolbar, getTitle(), this);
+ mActionBar.invalidateOptionsMenu();
}
/**
@@ -2328,8 +2450,12 @@ public class Activity extends ContextThemeWrapper
* but you can override this to do whatever you want.
*/
public void onBackPressed() {
+ if (mActionBar != null && mActionBar.collapseActionView()) {
+ return;
+ }
+
if (!mFragments.popBackStackImmediate()) {
- finishWithTransition();
+ finishAfterTransition();
}
}
@@ -2539,6 +2665,14 @@ public class Activity extends ContextThemeWrapper
*/
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
+
+ // Let action bars open menus in response to the menu key prioritized over
+ // the window handling it
+ if (event.getKeyCode() == KeyEvent.KEYCODE_MENU &&
+ mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
+ return true;
+ }
+
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
@@ -2786,7 +2920,9 @@ public class Activity extends ContextThemeWrapper
* time it needs to be displayed.
*/
public void invalidateOptionsMenu() {
- mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+ if (mActionBar == null || !mActionBar.invalidateOptionsMenu()) {
+ mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+ }
}
/**
@@ -2996,7 +3132,9 @@ public class Activity extends ContextThemeWrapper
* open, this method does nothing.
*/
public void openOptionsMenu() {
- mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+ if (mActionBar == null || !mActionBar.openOptionsMenu()) {
+ mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+ }
}
/**
@@ -3498,14 +3636,16 @@ public class Activity extends ContextThemeWrapper
theme.applyStyle(resid, false);
}
- // Get the primary color and update the RecentsActivityValues for this activity
- TypedArray a = getTheme().obtainStyledAttributes(com.android.internal.R.styleable.Theme);
- int colorPrimary = a.getColor(com.android.internal.R.styleable.Theme_colorPrimary, 0);
- a.recycle();
- if (colorPrimary != 0) {
- ActivityManager.RecentsActivityValues v = new ActivityManager.RecentsActivityValues();
- v.colorPrimary = colorPrimary;
- setRecentsActivityValues(v);
+ // Get the primary color and update the TaskDescription for this activity
+ if (theme != null) {
+ TypedArray a = theme.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
+ int colorPrimary = a.getColor(com.android.internal.R.styleable.Theme_colorPrimary, 0);
+ a.recycle();
+ if (colorPrimary != 0) {
+ ActivityManager.TaskDescription v = new ActivityManager.TaskDescription(null, null,
+ colorPrimary);
+ setTaskDescription(v);
+ }
}
}
@@ -3524,7 +3664,7 @@ public class Activity extends ContextThemeWrapper
public void startActivityForResult(Intent intent, int requestCode) {
Bundle options = null;
if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
- options = ActivityOptions.makeSceneTransitionAnimation(mWindow, null).toBundle();
+ options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle();
}
startActivityForResult(intent, requestCode, options);
}
@@ -3565,9 +3705,7 @@ public class Activity extends ContextThemeWrapper
*/
public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
if (options != null) {
- ActivityOptions activityOptions = new ActivityOptions(options);
- activityOptions.dispatchStartExit();
- mCalledActivityOptions = activityOptions;
+ mActivityTransitionState.startExitOutTransition(this, options);
}
if (mParent == null) {
Instrumentation.ActivityResult ar =
@@ -4079,7 +4217,11 @@ public class Activity extends ContextThemeWrapper
*/
public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent,
int requestCode) {
- startActivityFromFragment(fragment, intent, requestCode, null);
+ Bundle options = null;
+ if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
+ options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle();
+ }
+ startActivityFromFragment(fragment, intent, requestCode, options);
}
/**
@@ -4104,6 +4246,9 @@ public class Activity extends ContextThemeWrapper
*/
public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent,
int requestCode, @Nullable Bundle options) {
+ if (options != null) {
+ mActivityTransitionState.startExitOutTransition(this, options);
+ }
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, fragment,
@@ -4433,13 +4578,10 @@ public class Activity extends ContextThemeWrapper
* 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)
+ * @see android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, android.util.Pair[])
*/
- public void finishWithTransition() {
- if (mEnterTransitionCoordinator != null) {
- mEnterTransitionCoordinator.startExit();
- } else {
+ public void finishAfterTransition() {
+ if (!mActivityTransitionState.startExitBackTransition(this)) {
finish();
}
}
@@ -4517,6 +4659,27 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Called when an activity you launched with an activity transition exposes this
+ * Activity through a returning activity transition, giving you the resultCode
+ * and any additional data from it. This method will only be called if the activity
+ * set a result code other than {@link #RESULT_CANCELED} and it supports activity
+ * transitions with {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * <p>The purpose of this function is to let the called Activity send a hint about
+ * its state so that this underlying Activity can prepare to be exposed. A call to
+ * this method does not guarantee that the called Activity has or will be exiting soon.
+ * It only indicates that it will expose this Activity's Window and it has
+ * some data to pass to prepare it.</p>
+ *
+ * @param resultCode The integer result code returned by the child activity
+ * through its setResult().
+ * @param data An Intent, which can return result data to the caller
+ * (various data can be attached to Intent "extras").
+ */
+ protected void onActivityReenter(int resultCode, Intent data) {
+ }
+
+ /**
* Create a new PendingIntent object which you can hand to others
* for them to use to send result data back to your
* {@link #onActivityResult} callback. The created object will be either
@@ -4793,27 +4956,30 @@ public class Activity extends ContextThemeWrapper
}
/**
- * Sets information describing this Activity for presentation inside the Recents System UI. When
- * {@link ActivityManager#getRecentTasks} is called, the activities of each task are
- * traversed in order from the topmost activity to the bottommost. The traversal continues for
- * each property until a suitable value is found. For each task those values will be returned in
- * {@link android.app.ActivityManager.RecentsActivityValues}.
+ * Sets information describing the task with this activity for presentation inside the Recents
+ * System UI. When {@link ActivityManager#getRecentTasks} is called, the activities of each task
+ * are traversed in order from the topmost activity to the bottommost. The traversal continues
+ * for each property until a suitable value is found. For each task the taskDescription will be
+ * returned in {@link android.app.ActivityManager.TaskDescription}.
*
* @see ActivityManager#getRecentTasks
- * @see android.app.ActivityManager.RecentsActivityValues
+ * @see android.app.ActivityManager.TaskDescription
*
- * @param values The Recents values that describe this activity.
+ * @param taskDescription The TaskDescription properties that describe the task with this activity
*/
- public void setRecentsActivityValues(ActivityManager.RecentsActivityValues values) {
- ActivityManager.RecentsActivityValues activityValues =
- new ActivityManager.RecentsActivityValues(values);
- // Scale the icon down to something reasonable
- if (values.icon != null) {
+ public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
+ ActivityManager.TaskDescription td;
+ // Scale the icon down to something reasonable if it is provided
+ if (taskDescription.getIcon() != null) {
final int size = ActivityManager.getLauncherLargeIconSizeInner(this);
- activityValues.icon = Bitmap.createScaledBitmap(values.icon, size, size, true);
+ final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size, true);
+ td = new ActivityManager.TaskDescription(taskDescription.getLabel(), icon,
+ taskDescription.getPrimaryColor());
+ } else {
+ td = taskDescription;
}
try {
- ActivityManagerNative.getDefault().setRecentsActivityValues(mToken, activityValues);
+ ActivityManagerNative.getDefault().setTaskDescription(mToken, td);
} catch (RemoteException e) {
}
}
@@ -5120,7 +5286,8 @@ public class Activity extends ContextThemeWrapper
* This call has no effect on non-translucent activities or on activities with the
* {@link android.R.attr#windowIsFloating} attribute.
*
- * @see #convertToTranslucent(TranslucentConversionListener)
+ * @see #convertToTranslucent(android.app.Activity.TranslucentConversionListener,
+ * ActivityOptions)
* @see TranslucentConversionListener
*
* @hide
@@ -5151,19 +5318,29 @@ public class Activity extends ContextThemeWrapper
*
* @param callback the method to call when all visible Activities behind this one have been
* drawn and it is safe to make this Activity translucent again.
+ * @param options activity options delivered to the activity below this one. The options
+ * are retrieved using {@link #getActivityOptions}.
*
* @see #convertFromTranslucent()
* @see TranslucentConversionListener
*
* @hide
*/
- public void convertToTranslucent(TranslucentConversionListener callback) {
+ void convertToTranslucent(TranslucentConversionListener callback, ActivityOptions options) {
+ boolean drawComplete;
try {
mTranslucentCallback = callback;
mChangeCanvasToTranslucent =
- ActivityManagerNative.getDefault().convertToTranslucent(mToken);
+ ActivityManagerNative.getDefault().convertToTranslucent(mToken, options);
+ drawComplete = true;
} catch (RemoteException e) {
- // pass
+ // Make callback return as though it timed out.
+ mChangeCanvasToTranslucent = false;
+ drawComplete = false;
+ }
+ if (!mChangeCanvasToTranslucent && mTranslucentCallback != null) {
+ // Window is already translucent.
+ mTranslucentCallback.onTranslucentConversionComplete(drawComplete);
}
}
@@ -5179,6 +5356,22 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Retrieve the ActivityOptions passed in from the launching activity or passed back
+ * from an activity launched by this activity in its call to {@link
+ * #convertToTranslucent(TranslucentConversionListener, ActivityOptions)}
+ *
+ * @return The ActivityOptions passed to {@link #convertToTranslucent}.
+ * @hide
+ */
+ ActivityOptions getActivityOptions() {
+ try {
+ return ActivityManagerNative.getDefault().getActivityOptions(mToken);
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
* Adjust the current immersive mode setting.
*
* Note that changing this value will have no effect on the activity's
@@ -5392,18 +5585,34 @@ 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}.
+ * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+ * android.view.View, String)} was used to start an Activity, <var>listener</var>
+ * will be called to handle shared elements on the <i>launched</i> Activity. This requires
+ * {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * @param listener Used to manipulate shared element transitions on the launched Activity.
+ */
+ public void setEnterSharedElementListener(SharedElementListener listener) {
+ if (listener == null) {
+ listener = SharedElementListener.NULL_LISTENER;
+ }
+ mEnterTransitionListener = listener;
+ }
+
+ /**
+ * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+ * android.view.View, String)} was used to start an Activity, <var>listener</var>
+ * will be called to handle shared elements on the <i>launching</i> Activity. Most
+ * calls will only come when returning from the started Activity.
+ * This requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
*
- * @param listener Used to listen to events in the entering transition.
+ * @param listener Used to manipulate shared element transitions on the launching Activity.
*/
- public void setActivityTransitionListener(ActivityOptions.ActivityTransitionListener listener) {
- if (mEnterTransitionCoordinator != null) {
- mEnterTransitionCoordinator.setActivityTransitionListener(listener);
+ public void setExitSharedElementListener(SharedElementListener listener) {
+ if (listener == null) {
+ listener = SharedElementListener.NULL_LISTENER;
}
+ mExitTransitionListener = listener;
}
// ------------------ Internal API ------------------
@@ -5412,30 +5621,12 @@ public class Activity extends ContextThemeWrapper
mParent = parent;
}
- final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token,
- Application application, Intent intent, ActivityInfo info, CharSequence title,
- Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances,
- Configuration config) {
- attach(context, aThread, instr, token, 0, application, intent, info, title, parent, id,
- lastNonConfigurationInstances, config);
- }
-
- 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) {
- 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) {
+ Configuration config, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
mFragments.attachActivity(this, mContainer, null);
@@ -5464,8 +5655,14 @@ public class Activity extends ContextThemeWrapper
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
- mVoiceInteractor = voiceInteractor != null
- ? new VoiceInteractor(this, this, voiceInteractor, Looper.myLooper()) : null;
+ if (voiceInteractor != null) {
+ if (lastNonConfigurationInstances != null) {
+ mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
+ } else {
+ mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
+ Looper.myLooper());
+ }
+ }
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
@@ -5476,12 +5673,6 @@ 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 */
@@ -5489,14 +5680,27 @@ public class Activity extends ContextThemeWrapper
return mParent != null ? mParent.getActivityToken() : mToken;
}
- final void performCreate(Bundle icicle) {
- onCreate(icicle);
+ final void performCreateCommon() {
mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, false);
mFragments.dispatchActivityCreated();
+ mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
+ }
+
+ final void performCreate(Bundle icicle) {
+ onCreate(icicle);
+ mActivityTransitionState.readState(icicle);
+ performCreateCommon();
+ }
+
+ final void performCreate(Bundle icicle, PersistableBundle persistentState) {
+ onCreate(icicle, persistentState);
+ mActivityTransitionState.readState(icicle);
+ performCreateCommon();
}
final void performStart() {
+ mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
mFragments.noteStateNotSaved();
mCalled = false;
mFragments.execPendingActions();
@@ -5519,6 +5723,7 @@ public class Activity extends ContextThemeWrapper
lm.doReportStart();
}
}
+ mActivityTransitionState.enterReady(this);
}
final void performRestart() {
@@ -5666,6 +5871,9 @@ public class Activity extends ContextThemeWrapper
if (mLoaderManager != null) {
mLoaderManager.doDestroy();
}
+ if (mVoiceInteractor != null) {
+ mVoiceInteractor.detachActivity();
+ }
}
/**
@@ -5729,7 +5937,8 @@ public class Activity extends ContextThemeWrapper
* have completed drawing. This is necessary only after an {@link Activity} has been made
* opaque using {@link Activity#convertFromTranslucent()} and before it has been drawn
* translucent again following a call to {@link
- * Activity#convertToTranslucent(TranslucentConversionListener)}.
+ * Activity#convertToTranslucent(android.app.Activity.TranslucentConversionListener,
+ * ActivityOptions)}
*
* @hide
*/
@@ -5743,7 +5952,7 @@ public class Activity extends ContextThemeWrapper
* occurred waiting for the Activity to complete drawing.
*
* @see Activity#convertFromTranslucent()
- * @see Activity#convertToTranslucent(TranslucentConversionListener)
+ * @see Activity#convertToTranslucent(TranslucentConversionListener, ActivityOptions)
*/
public void onTranslucentConversionComplete(boolean drawComplete);
}
diff --git a/core/java/android/app/ActivityManager.aidl b/core/java/android/app/ActivityManager.aidl
new file mode 100644
index 0000000..92350da
--- /dev/null
+++ b/core/java/android/app/ActivityManager.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.app;
+
+parcelable ActivityManager.RecentTaskInfo;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 044727d..788ac56 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -33,6 +33,7 @@ import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Debug;
@@ -52,6 +53,7 @@ import android.util.Slog;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -474,67 +476,86 @@ public class ActivityManager {
}
/**
- * Information you can set and retrieve about the current activity within Recents.
+ * Information you can set and retrieve about the current activity within the recent task list.
*/
- public static class RecentsActivityValues implements Parcelable {
- public CharSequence label;
- public Bitmap icon;
- public int colorPrimary;
-
- public RecentsActivityValues(RecentsActivityValues values) {
- copyFrom(values);
- }
+ public static class TaskDescription implements Parcelable {
+ private String mLabel;
+ private Bitmap mIcon;
+ private int mColorPrimary;
/**
- * Creates the RecentsActivityValues to the specified values.
+ * Creates the TaskDescription to the specified values.
*
- * @param label A label and description of the current state of this activity.
- * @param icon An icon that represents the current state of this activity.
- * @param color A color to override the theme's primary color.
+ * @param label A label and description of the current state of this task.
+ * @param icon An icon that represents the current state of this task.
+ * @param colorPrimary A color to override the theme's primary color. This color must be opaque.
*/
- public RecentsActivityValues(CharSequence label, Bitmap icon, int color) {
- this.label = label;
- this.icon = icon;
- this.colorPrimary = color;
+ public TaskDescription(String label, Bitmap icon, int colorPrimary) {
+ if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
+ throw new RuntimeException("A TaskDescription's primary color should be opaque");
+ }
+
+ mLabel = label;
+ mIcon = icon;
+ mColorPrimary = colorPrimary;
}
/**
- * Creates the RecentsActivityValues to the specified values.
+ * Creates the TaskDescription to the specified values.
*
* @param label A label and description of the current state of this activity.
* @param icon An icon that represents the current state of this activity.
*/
- public RecentsActivityValues(CharSequence label, Bitmap icon) {
+ public TaskDescription(String label, Bitmap icon) {
this(label, icon, 0);
}
/**
- * Creates the RecentsActivityValues to the specified values.
+ * Creates the TaskDescription to the specified values.
*
* @param label A label and description of the current state of this activity.
*/
- public RecentsActivityValues(CharSequence label) {
+ public TaskDescription(String label) {
this(label, null, 0);
}
- public RecentsActivityValues() {
+ /**
+ * Creates an empty TaskDescription.
+ */
+ public TaskDescription() {
this(null, null, 0);
}
- private RecentsActivityValues(Parcel source) {
+ /**
+ * Creates a copy of another TaskDescription.
+ */
+ public TaskDescription(TaskDescription td) {
+ this(td.getLabel(), td.getIcon(), td.getPrimaryColor());
+ }
+
+ private TaskDescription(Parcel source) {
readFromParcel(source);
}
/**
- * Do a shallow copy of another set of activity values.
- * @hide
+ * @return The label and description of the current state of this task.
*/
- public void copyFrom(RecentsActivityValues v) {
- if (v != null) {
- label = v.label;
- icon = v.icon;
- colorPrimary = v.colorPrimary;
- }
+ public String getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * @return The icon that represents the current state of this task.
+ */
+ public Bitmap getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * @return The color override on the theme's primary color.
+ */
+ public int getPrimaryColor() {
+ return mColorPrimary;
}
@Override
@@ -544,37 +565,41 @@ public class ActivityManager {
@Override
public void writeToParcel(Parcel dest, int flags) {
- TextUtils.writeToParcel(label, dest,
- Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
- if (icon == null) {
+ if (mLabel == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ dest.writeString(mLabel);
+ }
+ if (mIcon == null) {
dest.writeInt(0);
} else {
dest.writeInt(1);
- icon.writeToParcel(dest, 0);
+ mIcon.writeToParcel(dest, 0);
}
- dest.writeInt(colorPrimary);
+ dest.writeInt(mColorPrimary);
}
public void readFromParcel(Parcel source) {
- label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- icon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null;
- colorPrimary = source.readInt();
+ mLabel = source.readInt() > 0 ? source.readString() : null;
+ mIcon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null;
+ mColorPrimary = source.readInt();
}
- public static final Creator<RecentsActivityValues> CREATOR
- = new Creator<RecentsActivityValues>() {
- public RecentsActivityValues createFromParcel(Parcel source) {
- return new RecentsActivityValues(source);
+ public static final Creator<TaskDescription> CREATOR
+ = new Creator<TaskDescription>() {
+ public TaskDescription createFromParcel(Parcel source) {
+ return new TaskDescription(source);
}
- public RecentsActivityValues[] newArray(int size) {
- return new RecentsActivityValues[size];
+ public TaskDescription[] newArray(int size) {
+ return new TaskDescription[size];
}
};
@Override
public String toString() {
- return "RecentsActivityValues Label: " + label + " Icon: " + icon +
- " colorPrimary: " + colorPrimary;
+ return "TaskDescription Label: " + mLabel + " Icon: " + mIcon +
+ " colorPrimary: " + mColorPrimary;
}
}
@@ -628,9 +653,11 @@ public class ActivityManager {
/**
* The recent activity values for the highest activity in the stack to have set the values.
- * {@link Activity#setRecentsActivityValues(android.app.ActivityManager.RecentsActivityValues)}.
+ * {@link Activity#setTaskDescription(android.app.ActivityManager.TaskDescription)}.
+ *
+ * @hide
*/
- public RecentsActivityValues activityValues;
+ public TaskDescription taskDescription;
public RecentTaskInfo() {
}
@@ -653,9 +680,9 @@ public class ActivityManager {
ComponentName.writeToParcel(origActivity, dest);
TextUtils.writeToParcel(description, dest,
Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
- if (activityValues != null) {
+ if (taskDescription != null) {
dest.writeInt(1);
- activityValues.writeToParcel(dest, 0);
+ taskDescription.writeToParcel(dest, 0);
} else {
dest.writeInt(0);
}
@@ -669,8 +696,8 @@ public class ActivityManager {
baseIntent = source.readInt() > 0 ? Intent.CREATOR.createFromParcel(source) : null;
origActivity = ComponentName.readFromParcel(source);
description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- activityValues = source.readInt() > 0 ?
- RecentsActivityValues.CREATOR.createFromParcel(source) : null;
+ taskDescription = source.readInt() > 0 ?
+ TaskDescription.CREATOR.createFromParcel(source) : null;
stackId = source.readInt();
userId = source.readInt();
}
@@ -711,7 +738,7 @@ public class ActivityManager {
public static final int RECENT_INCLUDE_PROFILES = 0x0004;
/**
- * Return a list of the tasks that the user has recently launched, with
+ * <p></p>Return a list of the tasks that the user has recently launched, with
* the most recent being first and older ones after in order.
*
* <p><b>Note: this method is only intended for debugging and presenting
@@ -723,6 +750,15 @@ public class ActivityManager {
* same time, assumptions made about the meaning of the data here for
* purposes of control flow will be incorrect.</p>
*
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#L}, this method is
+ * no longer available to third party applications: as the introduction of
+ * document-centric recents means
+ * it can leak personal information to the caller. For backwards compatibility,
+ * it will still return a small subset of its data: at least the caller's
+ * own tasks (though see {@link #getAppTasks()} for the correct supported
+ * way to retrieve that information), and possibly some other tasks
+ * such as home that are known to not be sensitive.
+ *
* @param maxNum The maximum number of entries to return in the list. The
* actual number returned may be smaller, depending on how many tasks the
* user has started and the maximum number the system can remember.
@@ -735,6 +771,7 @@ public class ActivityManager {
* @throws SecurityException Throws SecurityException if the caller does
* not hold the {@link android.Manifest.permission#GET_TASKS} permission.
*/
+ @Deprecated
public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags)
throws SecurityException {
try {
@@ -879,7 +916,29 @@ public class ActivityManager {
readFromParcel(source);
}
}
-
+
+ /**
+ * Get the list of tasks associated with the calling application.
+ *
+ * @return The list of tasks associated with the application making this call.
+ * @throws SecurityException
+ */
+ public List<ActivityManager.AppTask> getAppTasks() {
+ ArrayList<AppTask> tasks = new ArrayList<AppTask>();
+ List<IAppTask> appTasks;
+ try {
+ appTasks = ActivityManagerNative.getDefault().getAppTasks();
+ } catch (RemoteException e) {
+ // System dead, we will be dead too soon!
+ return null;
+ }
+ int numAppTasks = appTasks.size();
+ for (int i = 0; i < numAppTasks; i++) {
+ tasks.add(new AppTask(appTasks.get(i)));
+ }
+ return tasks;
+ }
+
/**
* Return a list of the tasks that are currently running, with
* the most recent being first and older ones after in order. Note that
@@ -897,6 +956,14 @@ public class ActivityManager {
* same time, assumptions made about the meaning of the data here for
* purposes of control flow will be incorrect.</p>
*
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#L}, this method
+ * is no longer available to third party
+ * applications: the introduction of document-centric recents means
+ * it can leak person information to the caller. For backwards compatibility,
+ * it will still retu rn a small subset of its data: at least the caller's
+ * own tasks, and possibly some other tasks
+ * such as home that are known to not be sensitive.
+ *
* @param maxNum The maximum number of entries to return in the list. The
* actual number returned may be smaller, depending on how many tasks the
* user has started.
@@ -907,6 +974,7 @@ public class ActivityManager {
* @throws SecurityException Throws SecurityException if the caller does
* not hold the {@link android.Manifest.permission#GET_TASKS} permission.
*/
+ @Deprecated
public List<RunningTaskInfo> getRunningTasks(int maxNum)
throws SecurityException {
try {
@@ -2382,4 +2450,42 @@ public class ActivityManager {
return false;
}
}
+
+ /**
+ * The AppTask allows you to manage your own application's tasks.
+ * See {@link android.app.ActivityManager#getAppTasks()}
+ */
+ public static class AppTask {
+ private IAppTask mAppTaskImpl;
+
+ /** @hide */
+ public AppTask(IAppTask task) {
+ mAppTaskImpl = task;
+ }
+
+ /**
+ * Finishes all activities in this task and removes it from the recent tasks list.
+ */
+ public void finishAndRemoveTask() {
+ try {
+ mAppTaskImpl.finishAndRemoveTask();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Invalid AppTask", e);
+ }
+ }
+
+ /**
+ * Get the RecentTaskInfo associated with this task.
+ *
+ * @return The RecentTaskInfo for this task, or null if the task no longer exists.
+ */
+ public RecentTaskInfo getTaskInfo() {
+ try {
+ return mAppTaskImpl.getTaskInfo();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Invalid AppTask", e);
+ return null;
+ }
+ }
+ }
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 57da21e..56462ae 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -40,6 +40,7 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
@@ -454,7 +455,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
case ACTIVITY_PAUSED_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
- activityPaused(token);
+ PersistableBundle persistentState = data.readPersistableBundle();
+ activityPaused(token, persistentState);
reply.writeNoException();
return true;
}
@@ -463,10 +465,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
Bundle map = data.readBundle();
- Bitmap thumbnail = data.readInt() != 0
- ? Bitmap.CREATOR.createFromParcel(data) : null;
+ PersistableBundle persistentState = data.readPersistableBundle();
CharSequence description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(data);
- activityStopped(token, map, thumbnail, description);
+ activityStopped(token, map, persistentState, description);
reply.writeNoException();
return true;
}
@@ -505,6 +506,20 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case GET_APP_TASKS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ List<IAppTask> list = getAppTasks();
+ reply.writeNoException();
+ int N = list != null ? list.size() : -1;
+ reply.writeInt(N);
+ int i;
+ for (i=0; i<N; i++) {
+ IAppTask task = list.get(i);
+ reply.writeStrongBinder(task.asBinder());
+ }
+ return true;
+ }
+
case GET_TASKS_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int maxNum = data.readInt();
@@ -928,7 +943,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
b = data.readStrongBinder();
IUiAutomationConnection c = IUiAutomationConnection.Stub.asInterface(b);
int userId = data.readInt();
- boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId);
+ String abiOverride = data.readString();
+ boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId,
+ abiOverride);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
@@ -1113,7 +1130,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
int pid = data.readInt();
int uid = data.readInt();
int mode = data.readInt();
- int res = checkUriPermission(uri, pid, uid, mode);
+ int userId = data.readInt();
+ int res = checkUriPermission(uri, pid, uid, mode, userId);
reply.writeNoException();
reply.writeInt(res);
return true;
@@ -1138,7 +1156,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
String targetPkg = data.readString();
Uri uri = Uri.CREATOR.createFromParcel(data);
int mode = data.readInt();
- grantUriPermission(app, targetPkg, uri, mode);
+ int userId = data.readInt();
+ grantUriPermission(app, targetPkg, uri, mode, userId);
reply.writeNoException();
return true;
}
@@ -1149,7 +1168,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
IApplicationThread app = ApplicationThreadNative.asInterface(b);
Uri uri = Uri.CREATOR.createFromParcel(data);
int mode = data.readInt();
- revokeUriPermission(app, uri, mode);
+ int userId = data.readInt();
+ revokeUriPermission(app, uri, mode, userId);
reply.writeNoException();
return true;
}
@@ -1158,7 +1178,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
Uri uri = Uri.CREATOR.createFromParcel(data);
int mode = data.readInt();
- takePersistableUriPermission(uri, mode);
+ int userId = data.readInt();
+ takePersistableUriPermission(uri, mode, userId);
reply.writeNoException();
return true;
}
@@ -1167,7 +1188,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
Uri uri = Uri.CREATOR.createFromParcel(data);
int mode = data.readInt();
- releasePersistableUriPermission(uri, mode);
+ int userId = data.readInt();
+ releasePersistableUriPermission(uri, mode, userId);
reply.writeNoException();
return true;
}
@@ -1541,12 +1563,28 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
case CONVERT_TO_TRANSLUCENT_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
- boolean converted = convertToTranslucent(token);
+ final Bundle bundle;
+ if (data.readInt() == 0) {
+ bundle = null;
+ } else {
+ bundle = data.readBundle();
+ }
+ final ActivityOptions options = bundle == null ? null : new ActivityOptions(bundle);
+ boolean converted = convertToTranslucent(token, options);
reply.writeNoException();
reply.writeInt(converted ? 1 : 0);
return true;
}
+ case GET_ACTIVITY_OPTIONS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ final ActivityOptions options = getActivityOptions(token);
+ reply.writeNoException();
+ reply.writeBundle(options == null ? null : options.toBundle());
+ return true;
+ }
+
case SET_IMMERSIVE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -1601,7 +1639,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
String targetPkg = data.readString();
Uri uri = Uri.CREATOR.createFromParcel(data);
int mode = data.readInt();
- grantUriPermissionFromOwner(owner, fromUid, targetPkg, uri, mode);
+ int userId = data.readInt();
+ grantUriPermissionFromOwner(owner, fromUid, targetPkg, uri, mode, userId);
reply.writeNoException();
return true;
}
@@ -1611,10 +1650,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
IBinder owner = data.readStrongBinder();
Uri uri = null;
if (data.readInt() != 0) {
- Uri.CREATOR.createFromParcel(data);
+ uri = Uri.CREATOR.createFromParcel(data);
}
int mode = data.readInt();
- revokeUriPermissionFromOwner(owner, uri, mode);
+ int userId = data.readInt();
+ revokeUriPermissionFromOwner(owner, uri, mode, userId);
reply.writeNoException();
return true;
}
@@ -1625,7 +1665,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
String targetPkg = data.readString();
Uri uri = Uri.CREATOR.createFromParcel(data);
int modeFlags = data.readInt();
- int res = checkGrantUriPermission(callingUid, targetPkg, uri, modeFlags);
+ int userId = data.readInt();
+ int res = checkGrantUriPermission(callingUid, targetPkg, uri, modeFlags, userId);
reply.writeNoException();
reply.writeInt(res);
return true;
@@ -2119,12 +2160,12 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
- case SET_RECENTS_ACTIVITY_VALUES_TRANSACTION: {
+ case SET_TASK_DESCRIPTION_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
- ActivityManager.RecentsActivityValues values =
- ActivityManager.RecentsActivityValues.CREATOR.createFromParcel(data);
- setRecentsActivityValues(token, values);
+ ActivityManager.TaskDescription values =
+ ActivityManager.TaskDescription.CREATOR.createFromParcel(data);
+ setTaskDescription(token, values);
reply.writeNoException();
return true;
}
@@ -2583,31 +2624,27 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
- public void activityPaused(IBinder token) throws RemoteException
+ public void activityPaused(IBinder token, PersistableBundle persistentState) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(token);
+ data.writePersistableBundle(persistentState);
mRemote.transact(ACTIVITY_PAUSED_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
}
public void activityStopped(IBinder token, Bundle state,
- Bitmap thumbnail, CharSequence description) throws RemoteException
+ PersistableBundle persistentState, CharSequence description) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(token);
data.writeBundle(state);
- if (thumbnail != null) {
- data.writeInt(1);
- thumbnail.writeToParcel(data, 0);
- } else {
- data.writeInt(0);
- }
+ data.writePersistableBundle(persistentState);
TextUtils.writeToParcel(description, data, 0);
mRemote.transact(ACTIVITY_STOPPED_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
reply.readException();
@@ -2662,6 +2699,26 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return res;
}
+ public List<IAppTask> getAppTasks() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(GET_APP_TASKS_TRANSACTION, data, reply, 0);
+ reply.readException();
+ ArrayList<IAppTask> list = null;
+ int N = reply.readInt();
+ if (N >= 0) {
+ list = new ArrayList<IAppTask>();
+ while (N > 0) {
+ IAppTask task = IAppTask.Stub.asInterface(reply.readStrongBinder());
+ list.add(task);
+ N--;
+ }
+ }
+ data.recycle();
+ reply.recycle();
+ return list;
+ }
public List getTasks(int maxNum, int flags) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -2677,7 +2734,7 @@ class ActivityManagerProxy implements IActivityManager
while (N > 0) {
ActivityManager.RunningTaskInfo info =
ActivityManager.RunningTaskInfo.CREATOR
- .createFromParcel(reply);
+ .createFromParcel(reply);
list.add(info);
N--;
}
@@ -3284,7 +3341,8 @@ class ActivityManagerProxy implements IActivityManager
public boolean startInstrumentation(ComponentName className, String profileFile,
int flags, Bundle arguments, IInstrumentationWatcher watcher,
- IUiAutomationConnection connection, int userId) throws RemoteException {
+ IUiAutomationConnection connection, int userId, String instructionSet)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3295,6 +3353,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeStrongBinder(watcher != null ? watcher.asBinder() : null);
data.writeStrongBinder(connection != null ? connection.asBinder() : null);
data.writeInt(userId);
+ data.writeString(instructionSet);
mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0);
reply.readException();
boolean res = reply.readInt() != 0;
@@ -3543,7 +3602,7 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return res;
}
- public int checkUriPermission(Uri uri, int pid, int uid, int mode)
+ public int checkUriPermission(Uri uri, int pid, int uid, int mode, int userId)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -3552,6 +3611,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeInt(pid);
data.writeInt(uid);
data.writeInt(mode);
+ data.writeInt(userId);
mRemote.transact(CHECK_URI_PERMISSION_TRANSACTION, data, reply, 0);
reply.readException();
int res = reply.readInt();
@@ -3560,7 +3620,7 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
public void grantUriPermission(IApplicationThread caller, String targetPkg,
- Uri uri, int mode) throws RemoteException {
+ Uri uri, int mode, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3568,19 +3628,21 @@ class ActivityManagerProxy implements IActivityManager
data.writeString(targetPkg);
uri.writeToParcel(data, 0);
data.writeInt(mode);
+ data.writeInt(userId);
mRemote.transact(GRANT_URI_PERMISSION_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
}
public void revokeUriPermission(IApplicationThread caller, Uri uri,
- int mode) throws RemoteException {
+ int mode, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller.asBinder());
uri.writeToParcel(data, 0);
data.writeInt(mode);
+ data.writeInt(userId);
mRemote.transact(REVOKE_URI_PERMISSION_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
@@ -3588,12 +3650,14 @@ class ActivityManagerProxy implements IActivityManager
}
@Override
- public void takePersistableUriPermission(Uri uri, int mode) throws RemoteException {
+ public void takePersistableUriPermission(Uri uri, int mode, int userId)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
uri.writeToParcel(data, 0);
data.writeInt(mode);
+ data.writeInt(userId);
mRemote.transact(TAKE_PERSISTABLE_URI_PERMISSION_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
@@ -3601,12 +3665,14 @@ class ActivityManagerProxy implements IActivityManager
}
@Override
- public void releasePersistableUriPermission(Uri uri, int mode) throws RemoteException {
+ public void releasePersistableUriPermission(Uri uri, int mode, int userId)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
uri.writeToParcel(data, 0);
data.writeInt(mode);
+ data.writeInt(userId);
mRemote.transact(RELEASE_PERSISTABLE_URI_PERMISSION_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
@@ -4062,12 +4128,18 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
- public boolean convertToTranslucent(IBinder token)
+ public boolean convertToTranslucent(IBinder token, ActivityOptions options)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(token);
+ if (options == null) {
+ data.writeInt(0);
+ } else {
+ data.writeInt(1);
+ data.writeBundle(options.toBundle());
+ }
mRemote.transact(CONVERT_TO_TRANSLUCENT_TRANSACTION, data, reply, 0);
reply.readException();
boolean res = reply.readInt() != 0;
@@ -4076,6 +4148,20 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
+ public ActivityOptions getActivityOptions(IBinder token) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ mRemote.transact(GET_ACTIVITY_OPTIONS_TRANSACTION, data, reply, 0);
+ reply.readException();
+ Bundle bundle = reply.readBundle();
+ ActivityOptions options = bundle == null ? null : new ActivityOptions(bundle);
+ data.recycle();
+ reply.recycle();
+ return options;
+ }
+
public void setImmersive(IBinder token, boolean immersive)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -4160,7 +4246,7 @@ class ActivityManagerProxy implements IActivityManager
}
public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg,
- Uri uri, int mode) throws RemoteException {
+ Uri uri, int mode, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -4169,6 +4255,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeString(targetPkg);
uri.writeToParcel(data, 0);
data.writeInt(mode);
+ data.writeInt(userId);
mRemote.transact(GRANT_URI_PERMISSION_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
@@ -4176,7 +4263,7 @@ class ActivityManagerProxy implements IActivityManager
}
public void revokeUriPermissionFromOwner(IBinder owner, Uri uri,
- int mode) throws RemoteException {
+ int mode, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -4188,6 +4275,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeInt(0);
}
data.writeInt(mode);
+ data.writeInt(userId);
mRemote.transact(REVOKE_URI_PERMISSION_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
@@ -4195,7 +4283,7 @@ class ActivityManagerProxy implements IActivityManager
}
public int checkGrantUriPermission(int callingUid, String targetPkg,
- Uri uri, int modeFlags) throws RemoteException {
+ Uri uri, int modeFlags, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -4203,6 +4291,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeString(targetPkg);
uri.writeToParcel(data, 0);
data.writeInt(modeFlags);
+ data.writeInt(userId);
mRemote.transact(CHECK_GRANT_URI_PERMISSION_TRANSACTION, data, reply, 0);
reply.readException();
int res = reply.readInt();
@@ -4882,14 +4971,14 @@ class ActivityManagerProxy implements IActivityManager
}
@Override
- public void setRecentsActivityValues(IBinder token, ActivityManager.RecentsActivityValues values)
+ public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(token);
values.writeToParcel(data, 0);
- mRemote.transact(SET_RECENTS_ACTIVITY_VALUES_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+ mRemote.transact(SET_TASK_DESCRIPTION_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
reply.readException();
data.recycle();
reply.recycle();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index a49359f..a057c3e 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -17,18 +17,18 @@
package android.app;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Bitmap;
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.ArrayList;
import java.util.List;
import java.util.Map;
@@ -108,6 +108,12 @@ public class ActivityOptions {
private static final String KEY_TRANSITION_COMPLETE_LISTENER
= "android:transitionCompleteListener";
+ private static final String KEY_TRANSITION_IS_RETURNING = "android:transitionIsReturning";
+ private static final String KEY_TRANSITION_SHARED_ELEMENTS = "android:sharedElementNames";
+ private static final String KEY_LOCAL_SHARED_ELEMENTS = "android:localSharedElementNames";
+ private static final String KEY_RESULT_DATA = "android:resultData";
+ private static final String KEY_RESULT_CODE = "android:resultCode";
+
/** @hide */
public static final int ANIM_NONE = 0;
/** @hide */
@@ -131,7 +137,12 @@ public class ActivityOptions {
private int mStartWidth;
private int mStartHeight;
private IRemoteCallback mAnimationStartedListener;
- private ResultReceiver mExitReceiver;
+ private ResultReceiver mTransitionReceiver;
+ private boolean mIsReturning;
+ private ArrayList<String> mSharedElementNames;
+ private ArrayList<String> mLocalSharedElementNames;
+ private Intent mResultData;
+ private int mResultCode;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -334,7 +345,7 @@ public class ActivityOptions {
* <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 activity The Activity whose window contains the 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
@@ -344,36 +355,70 @@ public class ActivityOptions {
* @see android.transition.Transition#setEpicenterCallback(
* android.transition.Transition.EpicenterCallback)
*/
- public static ActivityOptions makeSceneTransitionAnimation(Window window,
+ public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
View sharedElement, String sharedElementName) {
- return makeSceneTransitionAnimation(window,
- new SharedElementMappingListener(sharedElement, sharedElementName));
+ return makeSceneTransitionAnimation(activity, Pair.create(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()}
+ * Activity. The position of the first element in sharedElements
* 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.
+ * @param activity The Activity whose window contains the shared elements.
+ * @param sharedElements The names of the shared elements to transfer to the called
+ * Activity and their associated Views. The Views must each have
+ * a unique shared element name.
* @return Returns a new ActivityOptions object that you can use to
* supply these options as the options Bundle when starting an activity.
+ * Returns null if the Window does not have {@link Window#FEATURE_CONTENT_TRANSITIONS}.
* @see android.transition.Transition#setEpicenterCallback(
* android.transition.Transition.EpicenterCallback)
*/
- public static ActivityOptions makeSceneTransitionAnimation(Window window,
- ActivityTransitionListener listener) {
+ public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
+ Pair<View, String>... sharedElements) {
+ if (!activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
+ return null;
+ }
ActivityOptions opts = new ActivityOptions();
opts.mAnimationType = ANIM_SCENE_TRANSITION;
- ExitTransitionCoordinator exit = new ExitTransitionCoordinator(window, listener);
- opts.mExitReceiver = exit;
+
+ ArrayList<String> names = new ArrayList<String>();
+ ArrayList<String> mappedNames = new ArrayList<String>();
+
+ if (sharedElements != null) {
+ for (int i = 0; i < sharedElements.length; i++) {
+ Pair<View, String> sharedElement = sharedElements[i];
+ names.add(sharedElement.second);
+ mappedNames.add(sharedElement.first.getViewName());
+ }
+ }
+
+ ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, names, names,
+ mappedNames, false);
+ opts.mTransitionReceiver = exit;
+ opts.mSharedElementNames = names;
+ opts.mLocalSharedElementNames = mappedNames;
+ opts.mIsReturning = false;
+ return opts;
+ }
+
+ /** @hide */
+ public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
+ ExitTransitionCoordinator exitCoordinator, ArrayList<String> sharedElementNames,
+ int resultCode, Intent resultData) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mAnimationType = ANIM_SCENE_TRANSITION;
+ opts.mSharedElementNames = sharedElementNames;
+ opts.mTransitionReceiver = exitCoordinator;
+ opts.mIsReturning = true;
+ opts.mResultCode = resultCode;
+ opts.mResultData = resultData;
return opts;
}
@@ -409,7 +454,12 @@ public class ActivityOptions {
break;
case ANIM_SCENE_TRANSITION:
- mExitReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER);
+ mTransitionReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER);
+ mIsReturning = opts.getBoolean(KEY_TRANSITION_IS_RETURNING, false);
+ mSharedElementNames = opts.getStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS);
+ mLocalSharedElementNames = opts.getStringArrayList(KEY_LOCAL_SHARED_ELEMENTS);
+ mResultData = opts.getParcelable(KEY_RESULT_DATA);
+ mResultCode = opts.getInt(KEY_RESULT_CODE);
break;
}
}
@@ -466,15 +516,15 @@ public class ActivityOptions {
/** @hide */
public void dispatchActivityStopped() {
- if (mExitReceiver != null) {
- mExitReceiver.send(ActivityTransitionCoordinator.MSG_ACTIVITY_STOPPED, null);
+ if (mTransitionReceiver != null) {
+ mTransitionReceiver.send(ActivityTransitionCoordinator.MSG_ACTIVITY_STOPPED, null);
}
}
/** @hide */
public void dispatchStartExit() {
- if (mExitReceiver != null) {
- mExitReceiver.send(ActivityTransitionCoordinator.MSG_START_EXIT_TRANSITION, null);
+ if (mTransitionReceiver != null) {
+ mTransitionReceiver.send(ActivityTransitionCoordinator.MSG_START_EXIT_TRANSITION, null);
}
}
@@ -489,19 +539,37 @@ public class ActivityOptions {
}
/** @hide */
- public static void abort(Bundle options) {
- if (options != null) {
- (new ActivityOptions(options)).abort();
- }
+ public void setReturning() {
+ mIsReturning = true;
}
/** @hide */
- public EnterTransitionCoordinator createEnterActivityTransition(Activity activity) {
- EnterTransitionCoordinator coordinator = null;
- if (mAnimationType == ANIM_SCENE_TRANSITION) {
- coordinator = new EnterTransitionCoordinator(activity, mExitReceiver);
+ public boolean isReturning() {
+ return mIsReturning;
+ }
+
+ /** @hide */
+ public ArrayList<String> getSharedElementNames() {
+ return mSharedElementNames;
+ }
+
+ /** @hide */
+ public ArrayList<String> getLocalSharedElementNames() { return mLocalSharedElementNames; }
+
+ /** @hide */
+ public ResultReceiver getResultReceiver() { return mTransitionReceiver; }
+
+ /** @hide */
+ public int getResultCode() { return mResultCode; }
+
+ /** @hide */
+ public Intent getResultData() { return mResultData; }
+
+ /** @hide */
+ public static void abort(Bundle options) {
+ if (options != null) {
+ (new ActivityOptions(options)).abort();
}
- return coordinator;
}
/**
@@ -513,7 +581,12 @@ public class ActivityOptions {
if (otherOptions.mPackageName != null) {
mPackageName = otherOptions.mPackageName;
}
- mExitReceiver = null;
+ mTransitionReceiver = null;
+ mSharedElementNames = null;
+ mLocalSharedElementNames = null;
+ mIsReturning = false;
+ mResultData = null;
+ mResultCode = 0;
switch (otherOptions.mAnimationType) {
case ANIM_CUSTOM:
mAnimationType = otherOptions.mAnimationType;
@@ -558,9 +631,14 @@ public class ActivityOptions {
break;
case ANIM_SCENE_TRANSITION:
mAnimationType = otherOptions.mAnimationType;
- mExitReceiver = otherOptions.mExitReceiver;
+ mTransitionReceiver = otherOptions.mTransitionReceiver;
+ mSharedElementNames = otherOptions.mSharedElementNames;
+ mLocalSharedElementNames = otherOptions.mLocalSharedElementNames;
+ mIsReturning = otherOptions.mIsReturning;
mThumbnail = null;
mAnimationStartedListener = null;
+ mResultData = otherOptions.mResultData;
+ mResultCode = otherOptions.mResultCode;
break;
}
}
@@ -604,9 +682,14 @@ public class ActivityOptions {
break;
case ANIM_SCENE_TRANSITION:
b.putInt(KEY_ANIM_TYPE, mAnimationType);
- if (mExitReceiver != null) {
- b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mExitReceiver);
+ if (mTransitionReceiver != null) {
+ b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionReceiver);
}
+ b.putBoolean(KEY_TRANSITION_IS_RETURNING, mIsReturning);
+ b.putStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS, mSharedElementNames);
+ b.putStringArrayList(KEY_LOCAL_SHARED_ELEMENTS, mLocalSharedElementNames);
+ b.putParcelable(KEY_RESULT_DATA, mResultData);
+ b.putInt(KEY_RESULT_CODE, mResultCode);
break;
}
return b;
@@ -626,126 +709,4 @@ public class ActivityOptions {
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 b606088..d9adba3 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -47,6 +47,7 @@ import android.hardware.display.DisplayManagerGlobal;
import android.net.IConnectivityManager;
import android.net.Proxy;
import android.net.ProxyInfo;
+import android.net.Uri;
import android.opengl.GLUtils;
import android.os.AsyncTask;
import android.os.Binder;
@@ -56,11 +57,11 @@ 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;
import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -69,8 +70,6 @@ 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;
@@ -78,7 +77,6 @@ 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;
@@ -268,6 +266,7 @@ public final class ActivityThread {
Intent intent;
IVoiceInteractor voiceInteractor;
Bundle state;
+ PersistableBundle persistentState;
Activity activity;
Window window;
Activity parent;
@@ -295,7 +294,6 @@ public final class ActivityThread {
boolean isForward;
int pendingConfigChanges;
boolean onlyLocalRequest;
- Bundle activityOptions;
View mPendingRemoveWindow;
WindowManager mPendingRemoveWindowManager;
@@ -317,6 +315,10 @@ public final class ActivityThread {
return false;
}
+ public boolean isPersistable() {
+ return (activityInfo.flags & ActivityInfo.FLAG_PERSISTABLE) != 0;
+ }
+
public String toString() {
ComponentName componentName = intent != null ? intent.getComponent() : null;
return "ActivityRecord{"
@@ -590,8 +592,7 @@ public final class ActivityThread {
public final void scheduleResumeActivity(IBinder token, int processState,
boolean isForward, Bundle resumeArgs) {
updateProcessState(processState, false);
- sendMessage(H.RESUME_ACTIVITY, new Pair<IBinder, Bundle>(token, resumeArgs),
- isForward ? 1 : 0);
+ sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0);
}
public final void scheduleSendResult(IBinder token, List<ResultInfo> results) {
@@ -605,11 +606,10 @@ 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,
+ IVoiceInteractor voiceInteractor, int procState, Bundle state,
+ PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
- String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
- Bundle resumeArgs) {
+ String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) {
updateProcessState(procState, false);
@@ -622,6 +622,7 @@ public final class ActivityThread {
r.activityInfo = info;
r.compatInfo = compatInfo;
r.state = state;
+ r.persistentState = persistentState;
r.pendingResults = pendingResults;
r.pendingIntents = pendingNewIntents;
@@ -632,7 +633,6 @@ public final class ActivityThread {
r.profileFile = profileName;
r.profileFd = profileFd;
r.autoStopProfiler = autoStopProfiler;
- r.activityOptions = resumeArgs;
updatePendingConfiguration(curConfig);
@@ -835,7 +835,7 @@ public final class ActivityThread {
InetAddress.clearDnsCache();
}
- public void setHttpProxy(String host, String port, String exclList, String pacFileUrl) {
+ public void setHttpProxy(String host, String port, String exclList, Uri pacFileUrl) {
Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
}
@@ -1297,9 +1297,7 @@ public final class ActivityThread {
break;
case RESUME_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
- final Pair<IBinder, Bundle> resumeArgs = (Pair<IBinder, Bundle>) msg.obj;
- handleResumeActivity(resumeArgs.first, resumeArgs.second, true,
- msg.arg1 != 0, true);
+ handleResumeActivity((IBinder) msg.obj, true, msg.arg1 != 0, true);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case SEND_RESULT:
@@ -2079,7 +2077,7 @@ public final class ActivityThread {
+ ", comp=" + name
+ ", token=" + token);
}
- return performLaunchActivity(r, null, null);
+ return performLaunchActivity(r, null);
}
public final Activity getActivity(IBinder token) {
@@ -2132,8 +2130,7 @@ public final class ActivityThread {
sendMessage(H.CLEAN_UP_CONTEXT, cci);
}
- private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent,
- Bundle options) {
+ private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
ActivityInfo aInfo = r.activityInfo;
@@ -2191,7 +2188,7 @@ public final class ActivityThread {
+ r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
- r.embeddedID, r.lastNonConfigurationInstances, config, options,
+ r.embeddedID, r.lastNonConfigurationInstances, config,
r.voiceInteractor);
if (customIntent != null) {
@@ -2205,7 +2202,11 @@ public final class ActivityThread {
}
activity.mCalled = false;
- mInstrumentation.callActivityOnCreate(activity, r.state);
+ if (r.isPersistable()) {
+ mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
+ } else {
+ mInstrumentation.callActivityOnCreate(activity, r.state);
+ }
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
@@ -2218,13 +2219,23 @@ public final class ActivityThread {
r.stopped = false;
}
if (!r.activity.mFinished) {
- if (r.state != null) {
+ if (r.isPersistable()) {
+ if (r.state != null || r.persistentState != null) {
+ mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
+ r.persistentState);
+ }
+ } else if (r.state != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
if (!r.activity.mFinished) {
activity.mCalled = false;
- mInstrumentation.callActivityOnPostCreate(activity, r.state);
+ if (r.isPersistable()) {
+ mInstrumentation.callActivityOnPostCreate(activity, r.state,
+ r.persistentState);
+ } else {
+ mInstrumentation.callActivityOnPostCreate(activity, r.state);
+ }
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
@@ -2303,12 +2314,12 @@ public final class ActivityThread {
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
- Activity a = performLaunchActivity(r, customIntent, r.activityOptions);
+ Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
- handleResumeActivity(r.token, r.activityOptions, false, r.isForward,
+ handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
if (!r.activity.mFinished && r.startsNotResumed) {
@@ -2842,6 +2853,7 @@ public final class ActivityThread {
r.paused = false;
r.stopped = false;
r.state = null;
+ r.persistentState = null;
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
@@ -2867,7 +2879,7 @@ public final class ActivityThread {
r.mPendingRemoveWindowManager = null;
}
- final void handleResumeActivity(IBinder token, Bundle resumeArgs,
+ final void handleResumeActivity(IBinder token,
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.
@@ -2998,19 +3010,10 @@ public final class ActivityThread {
int h;
if (w < 0) {
Resources res = r.activity.getResources();
- 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);
- }
+ int wId = com.android.internal.R.dimen.thumbnail_width;
+ int hId = com.android.internal.R.dimen.thumbnail_height;
+ mThumbnailWidth = w = res.getDimensionPixelSize(wId);
+ mThumbnailHeight = h = res.getDimensionPixelSize(hId);
} else {
h = mThumbnailHeight;
}
@@ -3069,7 +3072,7 @@ public final class ActivityThread {
// Tell the activity manager we have paused.
try {
- ActivityManagerNative.getDefault().activityPaused(token);
+ ActivityManagerNative.getDefault().activityPaused(token, r.persistentState);
} catch (RemoteException ex) {
}
}
@@ -3099,17 +3102,13 @@ public final class ActivityThread {
+ r.intent.getComponent().toShortString());
Slog.e(TAG, e.getMessage(), e);
}
- Bundle state = null;
if (finished) {
r.activity.mFinished = true;
}
try {
// Next have the activity save its current state and managed dialogs...
if (!r.activity.mFinished && saveState) {
- state = new Bundle();
- state.setAllowFds(false);
- mInstrumentation.callActivityOnSaveInstanceState(r.activity, state);
- r.state = state;
+ callCallActivityOnSaveInstanceState(r);
}
// Now we are idle.
r.activity.mCalled = false;
@@ -3145,7 +3144,7 @@ public final class ActivityThread {
listeners.get(i).onPaused(r.activity);
}
- return state;
+ return !r.activity.mFinished && saveState ? r.state : null;
}
final void performStopActivity(IBinder token, boolean saveState) {
@@ -3156,7 +3155,7 @@ public final class ActivityThread {
private static class StopInfo implements Runnable {
ActivityClientRecord activity;
Bundle state;
- Bitmap thumbnail;
+ PersistableBundle persistentState;
CharSequence description;
@Override public void run() {
@@ -3164,7 +3163,7 @@ public final class ActivityThread {
try {
if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Reporting activity stopped: " + activity);
ActivityManagerNative.getDefault().activityStopped(
- activity.token, state, thumbnail, description);
+ activity.token, state, persistentState, description);
} catch (RemoteException ex) {
}
}
@@ -3203,7 +3202,6 @@ public final class ActivityThread {
private void performStopActivityInner(ActivityClientRecord r,
StopInfo info, boolean keepShown, boolean saveState) {
if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
- Bundle state = null;
if (r != null) {
if (!keepShown && r.stopped) {
if (r.activity.mFinished) {
@@ -3223,7 +3221,6 @@ public final class ActivityThread {
// First create a thumbnail for the activity...
// For now, don't create the thumbnail here; we are
// doing that by doing a screen snapshot.
- info.thumbnail = null; //createThumbnailBitmap(r);
info.description = r.activity.onCreateDescription();
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
@@ -3238,12 +3235,7 @@ public final class ActivityThread {
// Next have the activity save its current state and managed dialogs...
if (!r.activity.mFinished && saveState) {
if (r.state == null) {
- state = new Bundle();
- state.setAllowFds(false);
- mInstrumentation.callActivityOnSaveInstanceState(r.activity, state);
- r.state = state;
- } else {
- state = r.state;
+ callCallActivityOnSaveInstanceState(r);
}
}
@@ -3319,6 +3311,7 @@ public final class ActivityThread {
// manager to proceed and allow us to go fully into the background.
info.activity = r;
info.state = r.state;
+ info.persistentState = r.persistentState;
mH.post(info);
}
@@ -3775,9 +3768,7 @@ public final class ActivityThread {
performPauseActivity(r.token, false, r.isPreHoneycomb());
}
if (r.state == null && !r.stopped && !r.isPreHoneycomb()) {
- r.state = new Bundle();
- r.state.setAllowFds(false);
- mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
+ callCallActivityOnSaveInstanceState(r);
}
handleDestroyActivity(r.token, false, configChanges, true);
@@ -3802,11 +3793,22 @@ public final class ActivityThread {
}
}
r.startsNotResumed = tmp.startsNotResumed;
- r.activityOptions = null;
handleLaunchActivity(r, currentIntent);
}
+ private void callCallActivityOnSaveInstanceState(ActivityClientRecord r) {
+ r.state = new Bundle();
+ r.state.setAllowFds(false);
+ if (r.isPersistable()) {
+ r.persistentState = new PersistableBundle();
+ mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state,
+ r.persistentState);
+ } else {
+ mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
+ }
+ }
+
ArrayList<ComponentCallbacks2> collectComponentCallbacks(
boolean allActivities, Configuration newConfig) {
ArrayList<ComponentCallbacks2> callbacks
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index 3eb2fea..b739387 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -15,26 +15,22 @@
*/
package android.app;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
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.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewGroupOverlay;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.widget.ImageView;
@@ -82,15 +78,14 @@ import java.util.Collection;
* 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
+ * Typical finishAfterTransition goes like this:
+ * 1) finishAfterTransition() creates an ExitTransitionCoordinator and calls startExit()
+ * - The Window start transitioning to Translucent with a new ActivityOptions.
* - 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
+ * 2) The ActivityOptions is received by the Activity and an EnterTransitionCoordinator is created.
* - 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
@@ -98,21 +93,21 @@ import java.util.Collection;
* 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
+ * EnterTransitionCoordinator
+ * 6) MSG_TAKE_SHARED_ELEMENTS is received by 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.
- * - MSG_HIDE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator
- * 7) MSG_HIDE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator
+ * - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator
+ * 7) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator
* - The shared elements are made INVISIBLE
* 8) The exit transition completes in the finishing Activity.
- * - MSG_EXIT_TRANSITION_COMPLETE is sent to the ExitTransitionCoordinator.
+ * - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator.
* - finish() is called on the exiting Activity
- * 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the ExitTransitionCoordinator.
+ * 9) 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.
*/
@@ -120,30 +115,24 @@ 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";
+ static final String KEY_REMOTE_RECEIVER = "android:remoteReceiver";
+
+ protected static final String KEY_SCREEN_X = "shared_element:screenX";
+ protected static final String KEY_SCREEN_Y = "shared_element:screenY";
+ protected static final String KEY_TRANSLATION_Z = "shared_element:translationZ";
+ protected static final String KEY_WIDTH = "shared_element:width";
+ protected static final String KEY_HEIGHT = "shared_element:height";
+ protected static final String KEY_BITMAP = "shared_element:bitmap";
+ protected static final String KEY_SCALE_TYPE = "shared_element:scaleType";
+ protected static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix";
- 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";
- private static final String KEY_SCALE_TYPE = "shared_element:scaleType";
- private static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix";
+ // The background fade in/out duration. TODO: Enable tuning this.
+ public static final int FADE_BACKGROUND_DURATION_MS = 300;
- private static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values();
+ protected static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values();
/**
* Sent by the exiting coordinator (either EnterTransitionCoordinator
@@ -154,7 +143,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
* 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;
+ public static final int MSG_SET_REMOTE_RECEIVER = 100;
/**
* Sent by the entering coordinator to tell the exiting coordinator
@@ -165,17 +154,10 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
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;
+ public static final int MSG_ACTIVITY_STOPPED = 102;
/**
* Sent by the exiting coordinator (either EnterTransitionCoordinator
@@ -186,7 +168,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
* 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;
+ public static final int MSG_TAKE_SHARED_ELEMENTS = 103;
/**
* Sent by the exiting coordinator (either
@@ -196,348 +178,125 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
* remote coordinator. If it is false, it will trigger the enter
* transition to start.
*/
- public static final int MSG_EXIT_TRANSITION_COMPLETE = 105;
+ public static final int MSG_EXIT_TRANSITION_COMPLETE = 104;
/**
* 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());
- }
+ public static final int MSG_START_EXIT_TRANSITION = 105;
/**
- * Called when MSG_PREPARE_RESTORE is called. This will only be received by
- * ExitTransitionCoordinator.
+ * It took too long for a message from the entering Activity, so we canceled the transition.
*/
- protected void onPrepareRestore() {
- mListener.onEnterReady();
- }
+ public static final int MSG_CANCEL = 106;
/**
- * 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.
+ * When returning, this is the destination location for the shared element.
*/
- protected void onRemoteSceneExitComplete() {
- if (!allowOverlappingTransitions()) {
- Transition transition = beginTransition(mEnteringViews, false, true, true);
- onStartEnterTransition(transition, mEnteringViews);
- }
- mListener.onRemoteExitComplete();
- }
+ public static final int MSG_SHARED_ELEMENT_DESTINATION = 107;
/**
- * 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)
+ * Send the shared element positions.
*/
- 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);
- ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState =
- setSharedElementState(state, accepted);
- handleRejected(rejected);
-
- if (getViewsTransition() != null) {
- setViewVisibility(mEnteringViews, View.INVISIBLE);
- }
- setViewVisibility(mSharedElements, View.VISIBLE);
- Transition transition = beginTransition(mEnteringViews, true, allowOverlappingTransitions(),
- true);
- setOriginalImageViewState(originalImageViewState);
-
- 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) {
+ public static final int MSG_SEND_SHARED_ELEMENT_DESTINATION = 108;
+
+ final private Window mWindow;
+ final protected ArrayList<String> mAllSharedElementNames;
+ final protected ArrayList<View> mSharedElements = new ArrayList<View>();
+ final protected ArrayList<String> mSharedElementNames = new ArrayList<String>();
+ final protected ArrayList<View> mTransitioningViews = new ArrayList<View>();
+ final protected SharedElementListener mListener;
+ protected ResultReceiver mResultReceiver;
+ final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback();
+ final protected boolean mIsReturning;
+
+ public ActivityTransitionCoordinator(Window window,
+ ArrayList<String> allSharedElementNames,
+ ArrayList<String> accepted, ArrayList<String> localNames,
+ SharedElementListener listener, boolean isReturning) {
+ super(new Handler());
+ mWindow = window;
+ mListener = listener;
+ mAllSharedElementNames = allSharedElementNames;
+ mIsReturning = isReturning;
+ setSharedElements(accepted, localNames);
if (getViewsTransition() != null) {
- setViewVisibility(enteringViews, View.VISIBLE);
+ getDecor().captureTransitioningViews(mTransitioningViews);
+ mTransitioningViews.removeAll(mSharedElements);
}
- mEnteringViews = null;
- mListener.onStartEnterTransition(getSharedElementNames(), getSharedElements());
+ setEpicenter();
}
- /**
- * 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();
+ protected Window getWindow() {
+ return mWindow;
}
- /**
- * 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();
+ protected ViewGroup getDecor() {
+ return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView();
}
/**
- * 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.
+ * Sets the transition epicenter to the position of the first shared element.
*/
- 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 setEpicenter() {
+ View epicenter = null;
+ if (!mAllSharedElementNames.isEmpty() && !mSharedElementNames.isEmpty() &&
+ mAllSharedElementNames.get(0).equals(mSharedElementNames.get(0))) {
+ epicenter = mSharedElements.get(0);
}
+ setEpicenter(epicenter);
}
- protected void notifyExitTransitionComplete() {
- if (!mNotifiedExitTransitionComplete) {
- mNotifiedExitTransitionComplete = true;
- mRemoteResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
+ private void setEpicenter(View view) {
+ if (view == null) {
+ mEpicenterCallback.setEpicenter(null);
+ } else {
+ 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();
+ Rect epicenter = new Rect(left, top, right, bottom);
+ mEpicenterCallback.setEpicenter(epicenter);
}
}
- protected void notifyPrepareRestore() {
- mRemoteResultReceiver.send(MSG_PREPARE_RESTORE, null);
- }
-
- protected void setRemoteResultReceiver(ResultReceiver resultReceiver) {
- mRemoteResultReceiver = resultReceiver;
+ public ArrayList<String> getAcceptedNames() {
+ return mSharedElementNames;
}
- 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;
+ public ArrayList<String> getMappedNames() {
+ ArrayList<String> names = new ArrayList<String>(mSharedElements.size());
+ for (int i = 0; i < mSharedElements.size(); i++) {
+ names.add(mSharedElements.get(i).getViewName());
+ }
+ return names;
}
- 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);
+ public static void setViewVisibility(Collection<View> views, int visibility) {
+ if (views != null) {
+ for (View view : views) {
+ view.setVisibility(visibility);
}
}
}
- 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);
+ protected static Transition addTargets(Transition transition, Collection<View> views) {
+ if (transition == null || views == null || views.isEmpty()) {
+ return null;
}
- 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;
+ TransitionSet set = new TransitionSet();
+ set.addTransition(transition);
+ if (views != null) {
+ for (View view: views) {
+ set.addTarget(view);
+ }
}
+ return set;
}
- // private methods
-
- private Transition configureTransition(Transition transition) {
+ protected Transition configureTransition(Transition transition) {
if (transition != null) {
transition = transition.clone();
transition.setEpicenterCallback(mEpicenterCallback);
@@ -545,46 +304,105 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
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);
+ protected static Transition mergeTransitions(Transition transition1, Transition transition2) {
+ if (transition1 == null) {
+ return transition2;
+ } else if (transition2 == null) {
+ return transition1;
+ } else {
+ TransitionSet transitionSet = new TransitionSet();
+ transitionSet.addTransition(transition1);
+ transitionSet.addTransition(transition2);
+ return transitionSet;
+ }
+ }
+
+ private void setSharedElements(ArrayList<String> accepted, ArrayList<String> localNames) {
+ if (!mAllSharedElementNames.isEmpty()) {
+ ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
+ getDecor().findNamedViews(sharedElements);
+ if (accepted != null) {
+ for (int i = 0; i < localNames.size(); i++) {
+ String localName = localNames.get(i);
+ String acceptedName = accepted.get(i);
+ if (!localName.equals(acceptedName)) {
+ View view = sharedElements.remove(localName);
+ if (view != null) {
+ sharedElements.put(acceptedName, view);
+ }
+ }
+ }
+ }
+ sharedElements.retainAll(mAllSharedElementNames);
+ mListener.remapSharedElements(mAllSharedElementNames, sharedElements);
+ sharedElements.retainAll(mAllSharedElementNames);
+ for (int i = 0; i < mAllSharedElementNames.size(); i++) {
+ String name = mAllSharedElementNames.get(i);
+ View sharedElement = sharedElements.get(name);
+ if (sharedElement != null) {
+ mSharedElementNames.add(name);
+ mSharedElements.add(sharedElement);
}
- targetIndex++;
}
}
- for (int i = mSharedElements.size() - 1; i >= targetIndex; i--) {
- mSharedElements.remove(i);
- mTargetSharedNames.remove(i);
+ }
+
+ protected void setResultReceiver(ResultReceiver resultReceiver) {
+ mResultReceiver = resultReceiver;
+ }
+
+ protected abstract Transition getViewsTransition();
+
+ private static void setSharedElementState(View view, String name, Bundle transitionArgs,
+ int[] parentLoc) {
+ Bundle sharedElementBundle = transitionArgs.getBundle(name);
+ if (sharedElementBundle == null) {
+ return;
}
- Rect epicenter = null;
- if (!mTargetSharedNames.isEmpty()
- && mTargetSharedNames.get(0).equals(sharedElementNames.get(0))) {
- epicenter = calcEpicenter(mSharedElements.get(0));
+
+ if (view instanceof ImageView) {
+ int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1);
+ if (scaleTypeInt >= 0) {
+ ImageView imageView = (ImageView) view;
+ ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt];
+ imageView.setScaleType(scaleType);
+ if (scaleType == ImageView.ScaleType.MATRIX) {
+ float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX);
+ Matrix matrix = new Matrix();
+ matrix.setValues(matrixValues);
+ imageView.setImageMatrix(matrix);
+ }
+ }
}
- mEpicenterCallback.setEpicenter(epicenter);
+
+ 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);
}
- private ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState(
- Bundle sharedElementState, final ArrayList<View> acceptedOverlayViews) {
+ protected ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState(
+ Bundle sharedElementState, final ArrayList<View> snapshots) {
ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState =
new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>();
- final int[] tempLoc = new int[2];
if (sharedElementState != null) {
- for (int i = 0; i < mSharedElements.size(); i++) {
+ int[] tempLoc = new int[2];
+ for (int i = 0; i < mSharedElementNames.size(); i++) {
View sharedElement = mSharedElements.get(i);
- String name = mTargetSharedNames.get(i);
+ String name = mSharedElementNames.get(i);
Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement,
name, sharedElementState);
if (originalState != null) {
@@ -593,20 +411,17 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
View parent = (View) sharedElement.getParent();
parent.getLocationOnScreen(tempLoc);
setSharedElementState(sharedElement, name, sharedElementState, tempLoc);
- sharedElement.requestLayout();
}
}
- mListener.onCaptureSharedElementStart(mTargetSharedNames, mSharedElements,
- acceptedOverlayViews);
+ mListener.setSharedElementStart(mSharedElementNames, mSharedElements, snapshots);
getDecor().getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
- mListener.onCaptureSharedElementEnd(mTargetSharedNames, mSharedElements,
- acceptedOverlayViews);
- mSharedElementTransitionStarted = true;
+ mListener.setSharedElementEnd(mSharedElementNames, mSharedElements,
+ snapshots);
return true;
}
}
@@ -620,6 +435,9 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
return null;
}
Bundle bundle = transitionArgs.getBundle(name);
+ if (bundle == null) {
+ return null;
+ }
int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1);
if (scaleTypeInt < 0) {
return null;
@@ -636,65 +454,62 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
return Pair.create(originalScaleType, originalMatrix);
}
- /**
- * 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;
+ protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) {
+ int numSharedElements = names.size();
+ if (numSharedElements == 0) {
+ return null;
}
-
- if (view instanceof ImageView) {
- int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1);
- if (scaleTypeInt >= 0) {
- ImageView imageView = (ImageView) view;
- ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt];
- imageView.setScaleType(scaleType);
- if (scaleType == ImageView.ScaleType.MATRIX) {
- float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX);
- Matrix matrix = new Matrix();
- matrix.setValues(matrixValues);
- imageView.setImageMatrix(matrix);
+ ArrayList<View> snapshots = new ArrayList<View>(numSharedElements);
+ Context context = getWindow().getContext();
+ int[] parentLoc = new int[2];
+ getDecor().getLocationOnScreen(parentLoc);
+ for (String name: names) {
+ Bundle sharedElementBundle = state.getBundle(name);
+ if (sharedElementBundle != null) {
+ Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP);
+ View snapshot = new View(context);
+ Resources resources = getWindow().getContext().getResources();
+ if (bitmap != null) {
+ snapshot.setBackground(new BitmapDrawable(resources, bitmap));
}
+ snapshot.setViewName(name);
+ setSharedElementState(snapshot, name, state, parentLoc);
+ snapshots.add(snapshot);
}
}
+ return snapshots;
+ }
- 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);
+ protected static void setOriginalImageViewState(
+ ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) {
+ for (int i = 0; i < originalState.size(); i++) {
+ ImageView imageView = originalState.keyAt(i);
+ Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i);
+ imageView.setScaleType(state.first);
+ imageView.setImageMatrix(state.second);
+ }
+ }
- int left = x - parentLoc[0];
- int top = y - parentLoc[1];
- int right = left + width;
- int bottom = top + height;
- view.layout(left, top, right, bottom);
+ protected Bundle captureSharedElementState() {
+ Bundle bundle = new Bundle();
+ int[] tempLoc = new int[2];
+ for (int i = 0; i < mSharedElementNames.size(); i++) {
+ View sharedElement = mSharedElements.get(i);
+ String name = mSharedElementNames.get(i);
+ captureSharedElementState(sharedElement, name, bundle, tempLoc);
+ }
+ return bundle;
}
/**
* 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 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[])
+ * @param tempLoc A temporary int[2] for capturing the current location of views.
*/
private static void captureSharedElementState(View view, String name, Bundle transitionArgs,
int[] tempLoc) {
@@ -707,17 +522,17 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
float scaleY = view.getScaleY();
sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]);
- int height= Math.round(view.getHeight() * scaleY);
+ 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);
+ if (width > 0 && height > 0) {
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ view.draw(canvas);
+ sharedElementBundle.putParcelable(KEY_BITMAP, bitmap);
+ }
if (view instanceof ImageView) {
ImageView imageView = (ImageView) view;
@@ -733,176 +548,6 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
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 void setOriginalImageViewState(
- ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) {
- for (int i = 0; i < originalState.size(); i++) {
- ImageView imageView = originalState.keyAt(i);
- Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i);
- imageView.setScaleType(state.first);
- imageView.setImageMatrix(state.second);
- }
- }
-
private static int scaleTypeToInt(ImageView.ScaleType scaleType) {
for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) {
if (scaleType == SCALE_TYPE_VALUES[i]) {
@@ -922,4 +567,5 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
return mEpicenter;
}
}
+
}
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
new file mode 100644
index 0000000..b32e9ad
--- /dev/null
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -0,0 +1,209 @@
+/*
+ * 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.os.ResultReceiver;
+import android.util.ArrayMap;
+import android.view.View;
+import android.view.Window;
+
+import java.util.ArrayList;
+
+/**
+ * This class contains all persistence-related functionality for Activity Transitions.
+ * Activities start exit and enter Activity Transitions through this class.
+ */
+class ActivityTransitionState {
+
+ private static final String ENTERING_SHARED_ELEMENTS = "android:enteringSharedElements";
+
+ private static final String ENTERING_MAPPED_FROM = "android:enteringMappedFrom";
+
+ private static final String ENTERING_MAPPED_TO = "android:enteringMappedTo";
+
+ private static final String EXITING_MAPPED_FROM = "android:exitingMappedFrom";
+
+ private static final String EXITING_MAPPED_TO = "android:exitingMappedTo";
+
+ /**
+ * The shared elements that the calling Activity has said that they transferred to this
+ * Activity.
+ */
+ private ArrayList<String> mEnteringNames;
+
+ /**
+ * The shared elements that this Activity as accepted and mapped to local Views.
+ */
+ private ArrayList<String> mEnteringFrom;
+
+ /**
+ * The names of local Views that are mapped to those elements in mEnteringFrom.
+ */
+ private ArrayList<String> mEnteringTo;
+
+ /**
+ * The names of shared elements that were shared to the called Activity.
+ */
+ private ArrayList<String> mExitingFrom;
+
+ /**
+ * The names of local Views that were shared out, mapped to those elements in mExitingFrom.
+ */
+ private ArrayList<String> mExitingTo;
+
+ /**
+ * The ActivityOptions used to call an Activity. Used to make the elements restore
+ * Visibility of exited Views.
+ */
+ private ActivityOptions mCalledActivityOptions;
+
+ /**
+ * We must be able to cancel entering transitions to stop changing the Window to
+ * opaque when we exit before making the Window opaque.
+ */
+ private EnterTransitionCoordinator mEnterTransitionCoordinator;
+
+ /**
+ * ActivityOptions used on entering this Activity.
+ */
+ private ActivityOptions mEnterActivityOptions;
+
+ /**
+ * Has an exit transition been started? If so, we don't want to double-exit.
+ */
+ private boolean mHasExited;
+
+ public ActivityTransitionState() {
+ }
+
+ public void readState(Bundle bundle) {
+ if (bundle != null) {
+ if (mEnterTransitionCoordinator == null || mEnterTransitionCoordinator.isReturning()) {
+ mEnteringNames = bundle.getStringArrayList(ENTERING_SHARED_ELEMENTS);
+ mEnteringFrom = bundle.getStringArrayList(ENTERING_MAPPED_FROM);
+ mEnteringTo = bundle.getStringArrayList(ENTERING_MAPPED_TO);
+ }
+ if (mEnterTransitionCoordinator == null) {
+ mExitingFrom = bundle.getStringArrayList(EXITING_MAPPED_FROM);
+ mExitingTo = bundle.getStringArrayList(EXITING_MAPPED_TO);
+ }
+ }
+ }
+
+ public void saveState(Bundle bundle) {
+ if (mEnteringNames != null) {
+ bundle.putStringArrayList(ENTERING_SHARED_ELEMENTS, mEnteringNames);
+ bundle.putStringArrayList(ENTERING_MAPPED_FROM, mEnteringFrom);
+ bundle.putStringArrayList(ENTERING_MAPPED_TO, mEnteringTo);
+ }
+ if (mExitingFrom != null) {
+ bundle.putStringArrayList(EXITING_MAPPED_FROM, mExitingFrom);
+ bundle.putStringArrayList(EXITING_MAPPED_TO, mExitingTo);
+ }
+ }
+
+ public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
+ if (activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)
+ && options != null && mEnterActivityOptions == null
+ && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+ mEnterActivityOptions = options;
+ if (mEnterActivityOptions.isReturning()) {
+ int result = mEnterActivityOptions.getResultCode();
+ if (result != 0) {
+ activity.onActivityReenter(result, mEnterActivityOptions.getResultData());
+ }
+ }
+ }
+ }
+
+ public void enterReady(Activity activity) {
+ if (mEnterActivityOptions == null) {
+ return;
+ }
+ mHasExited = false;
+ ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
+ ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
+ if (mEnterActivityOptions.isReturning()) {
+ restoreExitedViews();
+ activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
+ mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
+ resultReceiver, sharedElementNames, mExitingFrom, mExitingTo);
+ } else {
+ mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
+ resultReceiver, sharedElementNames, null, null);
+ mEnteringNames = sharedElementNames;
+ mEnteringFrom = mEnterTransitionCoordinator.getAcceptedNames();
+ mEnteringTo = mEnterTransitionCoordinator.getMappedNames();
+ }
+ mExitingFrom = null;
+ mExitingTo = null;
+ mEnterActivityOptions = null;
+ }
+
+ public void onStop() {
+ restoreExitedViews();
+ if (mEnterTransitionCoordinator != null) {
+ mEnterTransitionCoordinator.stop();
+ mEnterTransitionCoordinator = null;
+ }
+ }
+
+ public void onResume() {
+ restoreExitedViews();
+ }
+
+ private void restoreExitedViews() {
+ if (mCalledActivityOptions != null) {
+ mCalledActivityOptions.dispatchActivityStopped();
+ mCalledActivityOptions = null;
+ }
+ }
+
+ public boolean startExitBackTransition(Activity activity) {
+ if (mEnteringNames == null) {
+ return false;
+ } else {
+ if (!mHasExited) {
+ mHasExited = true;
+ if (mEnterTransitionCoordinator != null) {
+ mEnterTransitionCoordinator.stop();
+ mEnterTransitionCoordinator = null;
+ }
+ ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
+ activity.getWindow().getDecorView().findNamedViews(sharedElements);
+
+ ExitTransitionCoordinator exitCoordinator =
+ new ExitTransitionCoordinator(activity, mEnteringNames, mEnteringFrom,
+ mEnteringTo, true);
+ exitCoordinator.startExit(activity.mResultCode, activity.mResultData);
+ }
+ return true;
+ }
+ }
+
+ public void startExitOutTransition(Activity activity, Bundle options) {
+ if (!activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
+ return;
+ }
+ mCalledActivityOptions = new ActivityOptions(options);
+ if (mCalledActivityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+ mExitingFrom = mCalledActivityOptions.getSharedElementNames();
+ mExitingTo = mCalledActivityOptions.getLocalSharedElementNames();
+ mCalledActivityOptions.dispatchStartExit();
+ }
+ }
+}
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index ab148a9..4ce7835 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -92,6 +92,18 @@ public class AlertDialog extends Dialog implements DialogInterface {
* the device's default alert theme with a light background.
*/
public static final int THEME_DEVICE_DEFAULT_LIGHT = 5;
+
+ /**
+ * No layout hint.
+ * @hide
+ */
+ public static final int LAYOUT_HINT_NONE = 0;
+
+ /**
+ * Hint layout to the side.
+ * @hide
+ */
+ public static final int LAYOUT_HINT_SIDE = 1;
protected AlertDialog(Context context) {
this(context, resolveDialogTheme(context, 0), true);
@@ -208,6 +220,14 @@ public class AlertDialog extends Dialog implements DialogInterface {
}
/**
+ * Internal api to allow hinting for the best button panel layout.
+ * @hide
+ */
+ void setButtonPanelLayoutHint(int layoutHint) {
+ mAlert.setButtonPanelLayoutHint(layoutHint);
+ }
+
+ /**
* Set a message to be sent when a button is pressed.
*
* @param whichButton Which button to set the message for, can be one of
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d813dab..5867232 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -19,6 +19,7 @@ package android.app;
import android.Manifest;
import android.os.Binder;
import android.os.IBinder;
+import android.os.UserManager;
import android.util.ArrayMap;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IAppOpsCallback;
@@ -412,6 +413,58 @@ public class AppOpsManager {
};
/**
+ * Specifies whether an Op should be restricted by a user restriction.
+ * Each Op should be filled with a restriction string from UserManager or
+ * null to specify it is not affected by any user restriction.
+ */
+ private static String[] sOpRestrictions = new String[] {
+ null, //COARSE_LOCATION
+ null, //FINE_LOCATION
+ null, //GPS
+ null, //VIBRATE
+ null, //READ_CONTACTS
+ null, //WRITE_CONTACTS
+ null, //READ_CALL_LOG
+ null, //WRITE_CALL_LOG
+ null, //READ_CALENDAR
+ null, //WRITE_CALENDAR
+ null, //WIFI_SCAN
+ null, //POST_NOTIFICATION
+ null, //NEIGHBORING_CELLS
+ null, //CALL_PHONE
+ null, //READ_SMS
+ null, //WRITE_SMS
+ null, //RECEIVE_SMS
+ null, //RECEIVE_EMERGECY_SMS
+ null, //RECEIVE_MMS
+ null, //RECEIVE_WAP_PUSH
+ null, //SEND_SMS
+ null, //READ_ICC_SMS
+ null, //WRITE_ICC_SMS
+ null, //WRITE_SETTINGS
+ null, //SYSTEM_ALERT_WINDOW
+ null, //ACCESS_NOTIFICATIONS
+ null, //CAMERA
+ null, //RECORD_AUDIO
+ null, //PLAY_AUDIO
+ null, //READ_CLIPBOARD
+ null, //WRITE_CLIPBOARD
+ null, //TAKE_MEDIA_BUTTONS
+ null, //TAKE_AUDIO_FOCUS
+ UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_MASTER_VOLUME
+ UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_VOICE_VOLUME
+ UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_RING_VOLUME
+ UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_MEDIA_VOLUME
+ UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ALARM_VOLUME
+ UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_NOTIFICATION_VOLUME
+ UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_BLUETOOTH_VOLUME
+ null, //WAKE_LOCK
+ null, //MONITOR_LOCATION
+ null, //MONITOR_HIGH_POWER_LOCATION
+ null, //GET_USAGE_STATS
+ };
+
+ /**
* This specifies the default mode for each operation.
*/
private static int[] sOpDefaultMode = new int[] {
@@ -542,6 +595,10 @@ public class AppOpsManager {
throw new IllegalStateException("sOpDisableReset length " + sOpDisableReset.length
+ " should be " + _NUM_OP);
}
+ if (sOpRestrictions.length != _NUM_OP) {
+ throw new IllegalStateException("sOpRestrictions length " + sOpRestrictions.length
+ + " should be " + _NUM_OP);
+ }
for (int i=0; i<_NUM_OP; i++) {
if (sOpToString[i] != null) {
sOpStrToOp.put(sOpToString[i], i);
@@ -575,6 +632,14 @@ public class AppOpsManager {
}
/**
+ * Retrieve the user restriction associated with an operation, or null if there is not one.
+ * @hide
+ */
+ public static String opToRestriction(int op) {
+ return sOpRestrictions[op];
+ }
+
+ /**
* Retrieve the default mode for the operation.
* @hide
*/
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index efd3d86..84673d9 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -35,6 +35,7 @@ import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionGroupInfo;
@@ -1127,7 +1128,7 @@ final class ApplicationPackageManager extends PackageManager {
public void installPackage(Uri packageURI, PackageInstallObserver observer,
int flags, String installerPackageName) {
try {
- mPM.installPackageEtc(packageURI, null, observer.mObserver,
+ mPM.installPackageEtc(packageURI, null, observer.getBinder(),
flags, installerPackageName);
} catch (RemoteException e) {
// Should never happen!
@@ -1140,7 +1141,7 @@ final class ApplicationPackageManager extends PackageManager {
Uri verificationURI, ManifestDigest manifestDigest,
ContainerEncryptionParams encryptionParams) {
try {
- mPM.installPackageWithVerificationEtc(packageURI, null, observer.mObserver, flags,
+ mPM.installPackageWithVerificationEtc(packageURI, null, observer.getBinder(), flags,
installerPackageName, verificationURI, manifestDigest, encryptionParams);
} catch (RemoteException e) {
// Should never happen!
@@ -1153,7 +1154,7 @@ final class ApplicationPackageManager extends PackageManager {
VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
try {
mPM.installPackageWithVerificationAndEncryptionEtc(packageURI, null,
- observer.mObserver, flags, installerPackageName, verificationParams,
+ observer.getBinder(), flags, installerPackageName, verificationParams,
encryptionParams);
} catch (RemoteException e) {
// Should never happen!
@@ -1440,14 +1441,24 @@ final class ApplicationPackageManager extends PackageManager {
return null;
}
+ @Override
+ public PackageInstaller getPackageInstaller() {
+ try {
+ return new PackageInstaller(this, mPM.getPackageInstaller(), mContext.getUserId(),
+ mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
/**
* @hide
*/
@Override
- public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int userIdOrig,
- int userIdDest) {
+ public void addCrossProfileIntentFilter(IntentFilter filter, boolean removable,
+ int sourceUserId, int targetUserId) {
try {
- mPM.addForwardingIntentFilter(filter, removable, userIdOrig, userIdDest);
+ mPM.addCrossProfileIntentFilter(filter, removable, sourceUserId, targetUserId);
} catch (RemoteException e) {
// Should never happen!
}
@@ -1457,14 +1468,31 @@ final class ApplicationPackageManager extends PackageManager {
* @hide
*/
@Override
- public void clearForwardingIntentFilters(int userIdOrig) {
+ public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int sourceUserId,
+ int targetUserId) {
+ addCrossProfileIntentFilter(filter, removable, sourceUserId, targetUserId);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void clearCrossProfileIntentFilters(int sourceUserId) {
try {
- mPM.clearForwardingIntentFilters(userIdOrig);
+ mPM.clearCrossProfileIntentFilters(sourceUserId);
} catch (RemoteException e) {
// Should never happen!
}
}
+ /**
+ * @hide
+ */
+ @Override
+ public void clearForwardingIntentFilters(int sourceUserId) {
+ clearCrossProfileIntentFilters(sourceUserId);
+ }
+
private final ContextImpl mContext;
private final IPackageManager mPM;
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 7f2fb59..ef4099f 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -25,10 +25,12 @@ import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
import android.os.Parcelable;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.Parcel;
@@ -141,6 +143,7 @@ public abstract class ApplicationThreadNative extends Binder
data.readStrongBinder());
int procState = data.readInt();
Bundle state = data.readBundle();
+ PersistableBundle persistentState = data.readPersistableBundle();
List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
List<Intent> pi = data.createTypedArrayList(Intent.CREATOR);
boolean notResumed = data.readInt() != 0;
@@ -149,11 +152,10 @@ public abstract class ApplicationThreadNative extends Binder
ParcelFileDescriptor profileFd = data.readInt() != 0
? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null;
boolean autoStopProfiler = data.readInt() != 0;
- Bundle resumeArgs = data.readBundle();
scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo,
- voiceInteractor, procState, state,
- ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler,
- resumeArgs);
+ voiceInteractor, procState, state, persistentState,
+ ri, pi, notResumed, isForward, profileName, profileFd,
+ autoStopProfiler);
return true;
}
@@ -337,7 +339,7 @@ public abstract class ApplicationThreadNative extends Binder
final String proxy = data.readString();
final String port = data.readString();
final String exclList = data.readString();
- final String pacFileUrl = data.readString();
+ final Uri pacFileUrl = Uri.CREATOR.createFromParcel(data);
setHttpProxy(proxy, port, exclList, pacFileUrl);
return true;
}
@@ -731,11 +733,10 @@ 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,
+ IVoiceInteractor voiceInteractor, int procState, Bundle state,
+ PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
- String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
- Bundle resumeArgs)
+ String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler)
throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
@@ -748,6 +749,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeStrongBinder(voiceInteractor != null ? voiceInteractor.asBinder() : null);
data.writeInt(procState);
data.writeBundle(state);
+ data.writePersistableBundle(persistentState);
data.writeTypedList(pendingResults);
data.writeTypedList(pendingNewIntents);
data.writeInt(notResumed ? 1 : 0);
@@ -760,7 +762,6 @@ 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();
@@ -1005,13 +1006,13 @@ class ApplicationThreadProxy implements IApplicationThread {
}
public void setHttpProxy(String proxy, String port, String exclList,
- String pacFileUrl) throws RemoteException {
+ Uri pacFileUrl) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeString(proxy);
data.writeString(port);
data.writeString(exclList);
- data.writeString(pacFileUrl);
+ pacFileUrl.writeToParcel(data, 0);
mRemote.transact(SET_HTTP_PROXY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
data.recycle();
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 801182d..e03224c 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -26,6 +26,7 @@ import com.android.internal.util.Preconditions;
import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
@@ -34,7 +35,9 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.IIntentReceiver;
import android.content.IntentSender;
+import android.content.IRestrictionsManager;
import android.content.ReceiverCallNotAllowedException;
+import android.content.RestrictionsManager;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
@@ -57,7 +60,9 @@ import android.hardware.ISerialManager;
import android.hardware.SerialManager;
import android.hardware.SystemSensorManager;
import android.hardware.hdmi.HdmiCecManager;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.IHdmiCecService;
+import android.hardware.hdmi.IHdmiControlService;
import android.hardware.camera2.CameraManager;
import android.hardware.display.DisplayManager;
import android.hardware.input.InputManager;
@@ -69,9 +74,13 @@ import android.location.ILocationManager;
import android.location.LocationManager;
import android.media.AudioManager;
import android.media.MediaRouter;
-import android.media.session.SessionManager;
+import android.media.session.MediaSessionManager;
+import android.media.tv.ITvInputManager;
+import android.media.tv.TvInputManager;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
+import android.net.EthernetManager;
+import android.net.IEthernetManager;
import android.net.INetworkPolicyManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
@@ -80,8 +89,8 @@ 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.passpoint.IWifiPasspointManager;
+import android.net.wifi.passpoint.WifiPasspointManager;
import android.net.wifi.p2p.IWifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager;
import android.nfc.NfcManager;
@@ -112,8 +121,6 @@ import android.service.fingerprint.FingerprintManager;
import android.service.fingerprint.FingerprintManagerReceiver;
import android.service.fingerprint.FingerprintService;
import android.telephony.TelephonyManager;
-import android.tv.ITvInputManager;
-import android.tv.TvInputManager;
import android.content.ClipboardManager;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
@@ -130,10 +137,12 @@ import android.view.textservice.TextServicesManager;
import android.accounts.AccountManager;
import android.accounts.IAccountManager;
import android.app.admin.DevicePolicyManager;
+import android.app.task.ITaskManager;
import android.app.trust.TrustManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.appwidget.IAppWidgetService.Stub;
import com.android.internal.os.IDropBoxManagerService;
import java.io.File;
@@ -244,6 +253,8 @@ class ContextImpl extends Context {
private File[] mExternalFilesDirs;
@GuardedBy("mSync")
private File[] mExternalCacheDirs;
+ @GuardedBy("mSync")
+ private File[] mExternalMediaDirs;
private static final String[] EMPTY_FILE_LIST = {};
@@ -381,6 +392,11 @@ class ContextImpl extends Context {
return new HdmiCecManager(IHdmiCecService.Stub.asInterface(b));
}});
+ registerService(HDMI_CONTROL_SERVICE, new StaticServiceFetcher() {
+ public Object createStaticService() {
+ IBinder b = ServiceManager.getService(HDMI_CONTROL_SERVICE);
+ return new HdmiControlManager(IHdmiControlService.Stub.asInterface(b));
+ }});
registerService(CLIPBOARD_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
@@ -577,11 +593,11 @@ class ContextImpl extends Context {
return new WifiManager(ctx.getOuterContext(), service);
}});
- registerService(WIFI_HOTSPOT_SERVICE, new ServiceFetcher() {
+ registerService(WIFI_PASSPOINT_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);
+ IBinder b = ServiceManager.getService(WIFI_PASSPOINT_SERVICE);
+ IWifiPasspointManager service = IWifiPasspointManager.Stub.asInterface(b);
+ return new WifiPasspointManager(ctx.getOuterContext(), service);
}});
registerService(WIFI_P2P_SERVICE, new ServiceFetcher() {
@@ -598,6 +614,13 @@ class ContextImpl extends Context {
return new WifiScanner(ctx.getOuterContext(), service);
}});
+ registerService(ETHERNET_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(ETHERNET_SERVICE);
+ IEthernetManager service = IEthernetManager.Stub.asInterface(b);
+ return new EthernetManager(ctx.getOuterContext(), service);
+ }});
+
registerService(WINDOW_SERVICE, new ServiceFetcher() {
Display mDefaultDisplay;
public Object getService(ContextImpl ctx) {
@@ -641,6 +664,13 @@ class ContextImpl extends Context {
}
});
+ registerService(RESTRICTIONS_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(RESTRICTIONS_SERVICE);
+ IRestrictionsManager service = IRestrictionsManager.Stub.asInterface(b);
+ return new RestrictionsManager(ctx, service);
+ }
+ });
registerService(PRINT_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE);
@@ -656,7 +686,7 @@ class ContextImpl extends Context {
registerService(MEDIA_SESSION_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
- return new SessionManager(ctx);
+ return new MediaSessionManager(ctx);
}
});
registerService(TRUST_SERVICE, new ServiceFetcher() {
@@ -683,6 +713,12 @@ class ContextImpl extends Context {
public Object createService(ContextImpl ctx) {
return new UsageStatsManager(ctx.getOuterContext());
}});
+
+ registerService(TASK_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(TASK_SERVICE);
+ return new TaskManagerImpl(ITaskManager.Stub.asInterface(b));
+ }});
}
static ContextImpl getImpl(Context context) {
@@ -1014,6 +1050,18 @@ class ContextImpl extends Context {
}
@Override
+ public File[] getExternalMediaDirs() {
+ synchronized (mSync) {
+ if (mExternalMediaDirs == null) {
+ mExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
+ }
+
+ // Create dirs if needed
+ return ensureDirsExistOrFilter(mExternalMediaDirs);
+ }
+ }
+
+ @Override
public File getFileStreamPath(String name) {
return makeFilename(getFilesDir(), name);
}
@@ -1344,6 +1392,15 @@ class ContextImpl extends Context {
public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
int initialCode, String initialData, Bundle initialExtras) {
+ sendOrderedBroadcastAsUser(intent, user, receiverPermission, AppOpsManager.OP_NONE,
+ resultReceiver, scheduler, initialCode, initialData, initialExtras);
+ }
+
+ @Override
+ public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
+ Handler scheduler,
+ int initialCode, String initialData, Bundle initialExtras) {
IIntentReceiver rd = null;
if (resultReceiver != null) {
if (mPackageInfo != null) {
@@ -1367,7 +1424,7 @@ class ContextImpl extends Context {
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, rd,
initialCode, initialData, initialExtras, receiverPermission,
- AppOpsManager.OP_NONE, true, false, user.getIdentifier());
+ appOp, true, false, user.getIdentifier());
} catch (RemoteException e) {
}
}
@@ -1707,7 +1764,8 @@ class ContextImpl extends Context {
arguments.setAllowFds(false);
}
return ActivityManagerNative.getDefault().startInstrumentation(
- className, profileFile, 0, arguments, null, null, getUserId());
+ className, profileFile, 0, arguments, null, null, getUserId(),
+ null /* ABI override */);
} catch (RemoteException e) {
// System has crashed, nothing we can do.
}
@@ -1818,8 +1876,8 @@ class ContextImpl extends Context {
public void grantUriPermission(String toPackage, Uri uri, int modeFlags) {
try {
ActivityManagerNative.getDefault().grantUriPermission(
- mMainThread.getApplicationThread(), toPackage, uri,
- modeFlags);
+ mMainThread.getApplicationThread(), toPackage,
+ ContentProvider.getUriWithoutUserId(uri), modeFlags, resolveUserId(uri));
} catch (RemoteException e) {
}
}
@@ -1828,8 +1886,8 @@ class ContextImpl extends Context {
public void revokeUriPermission(Uri uri, int modeFlags) {
try {
ActivityManagerNative.getDefault().revokeUriPermission(
- mMainThread.getApplicationThread(), uri,
- modeFlags);
+ mMainThread.getApplicationThread(),
+ ContentProvider.getUriWithoutUserId(uri), modeFlags, resolveUserId(uri));
} catch (RemoteException e) {
}
}
@@ -1838,12 +1896,17 @@ class ContextImpl extends Context {
public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
try {
return ActivityManagerNative.getDefault().checkUriPermission(
- uri, pid, uid, modeFlags);
+ ContentProvider.getUriWithoutUserId(uri), pid, uid, modeFlags,
+ resolveUserId(uri));
} catch (RemoteException e) {
return PackageManager.PERMISSION_DENIED;
}
}
+ private int resolveUserId(Uri uri) {
+ return ContentProvider.getUserIdFromUri(uri, getUserId());
+ }
+
@Override
public int checkCallingUriPermission(Uri uri, int modeFlags) {
int pid = Binder.getCallingPid();
@@ -2280,12 +2343,16 @@ class ContextImpl extends Context {
@Override
protected IContentProvider acquireProvider(Context context, String auth) {
- return mMainThread.acquireProvider(context, auth, mUser.getIdentifier(), true);
+ return mMainThread.acquireProvider(context,
+ ContentProvider.getAuthorityWithoutUserId(auth),
+ resolveUserIdFromAuthority(auth), true);
}
@Override
protected IContentProvider acquireExistingProvider(Context context, String auth) {
- return mMainThread.acquireExistingProvider(context, auth, mUser.getIdentifier(), true);
+ return mMainThread.acquireExistingProvider(context,
+ ContentProvider.getAuthorityWithoutUserId(auth),
+ resolveUserIdFromAuthority(auth), true);
}
@Override
@@ -2295,7 +2362,9 @@ class ContextImpl extends Context {
@Override
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
- return mMainThread.acquireProvider(c, auth, mUser.getIdentifier(), false);
+ return mMainThread.acquireProvider(c,
+ ContentProvider.getAuthorityWithoutUserId(auth),
+ resolveUserIdFromAuthority(auth), false);
}
@Override
@@ -2312,5 +2381,10 @@ class ContextImpl extends Context {
public void appNotRespondingViaProvider(IContentProvider icp) {
mMainThread.appNotRespondingViaProvider(icp.asBinder());
}
+
+ /** @hide */
+ protected int resolveUserIdFromAuthority(String auth) {
+ return ContentProvider.getUserIdFromAuthority(auth, mUser.getIdentifier());
+ }
}
}
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index d168800..26c2c30 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -107,6 +107,7 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener,
(LayoutInflater) themeContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.date_picker_dialog, null);
setView(view);
+ setButtonPanelLayoutHint(LAYOUT_HINT_SIDE);
mDatePicker = (DatePicker) view.findViewById(R.id.datePicker);
mDatePicker.init(year, monthOfYear, dayOfMonth, this);
updateTitle(year, monthOfYear, dayOfMonth);
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index cbb8359..4b052e7 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -18,270 +18,341 @@ package android.app;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
-import android.graphics.drawable.ColorDrawable;
+import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
import android.os.ResultReceiver;
import android.transition.Transition;
+import android.transition.TransitionManager;
+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;
/**
* 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.
+ * the enter scene and shared element transfer into the Scene, either during
+ * launch of an Activity or returning from a launched Activity.
*/
-class EnterTransitionCoordinator extends ActivityTransitionCoordinator
- implements ViewTreeObserver.OnPreDrawListener {
+class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
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;
+ private static final long MAX_WAIT_MS = 1000;
- /**
- * 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 boolean mSharedElementTransitionStarted;
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());
+ private boolean mHasStopped;
+ private Handler mHandler;
+ private boolean mIsCanceled;
+ private ObjectAnimator mBackgroundAnimator;
+ private boolean mIsExitTransitionComplete;
+
+ public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
+ ArrayList<String> sharedElementNames,
+ ArrayList<String> acceptedNames, ArrayList<String> mappedNames) {
+ super(activity.getWindow(), sharedElementNames, acceptedNames, mappedNames,
+ getListener(activity, acceptedNames), acceptedNames != null);
mActivity = activity;
- setRemoteResultReceiver(resultReceiver);
+ setResultReceiver(resultReceiver);
+ prepareEnter();
+ Bundle resultReceiverBundle = new Bundle();
+ resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this);
+ mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle);
+ if (mIsReturning) {
+ mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ cancel();
+ }
+ };
+ mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
+ send(MSG_SEND_SHARED_ELEMENT_DESTINATION, null);
+ }
}
- 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();
+ private void sendSharedElementDestination() {
+ ViewGroup decor = getDecor();
+ if (!decor.isLayoutRequested()) {
+ Bundle state = captureSharedElementState();
+ mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
+ } else {
+ getDecor().getViewTreeObserver()
+ .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
+ return true;
}
- }
- });
- 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);
+ private static SharedElementListener getListener(Activity activity,
+ ArrayList<String> acceptedNames) {
+ boolean isReturning = acceptedNames != null;
+ return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener;
}
@Override
- protected void sharedElementTransitionComplete(Bundle bundle) {
- notifySharedElementTransitionComplete(bundle);
- exitAfterSharedElementTransition();
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ switch (resultCode) {
+ case MSG_TAKE_SHARED_ELEMENTS:
+ if (!mIsCanceled) {
+ if (mHandler != null) {
+ mHandler.removeMessages(MSG_CANCEL);
+ }
+ onTakeSharedElements(resultData);
+ }
+ break;
+ case MSG_EXIT_TRANSITION_COMPLETE:
+ if (!mIsCanceled) {
+ mIsExitTransitionComplete = true;
+ if (mSharedElementTransitionStarted) {
+ onRemoteExitTransitionComplete();
+ }
+ }
+ break;
+ case MSG_CANCEL:
+ cancel();
+ break;
+ case MSG_SEND_SHARED_ELEMENT_DESTINATION:
+ sendSharedElementDestination();
+ break;
+ }
}
- @Override
- public boolean onPreDraw() {
- getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this);
- setEnteringViews(readyEnteringViews());
- notifySetListener();
- onPrepareRestore();
- return false;
+ private void cancel() {
+ if (!mIsCanceled) {
+ mIsCanceled = true;
+ if (getViewsTransition() == null) {
+ setViewVisibility(mSharedElements, View.VISIBLE);
+ } else {
+ mTransitioningViews.addAll(mSharedElements);
+ }
+ mSharedElementNames.clear();
+ mSharedElements.clear();
+ mAllSharedElementNames.clear();
+ onTakeSharedElements(null);
+ onRemoteExitTransitionComplete();
+ }
}
- @Override
- public void startExit() {
- if (!mExitTransitionStarted) {
- mExitTransitionStarted = true;
- startExitTransition(mEnteringSharedElementNames);
+ public boolean isReturning() {
+ return mIsReturning;
+ }
+
+ protected void prepareEnter() {
+ setViewVisibility(mSharedElements, View.INVISIBLE);
+ if (getViewsTransition() != null) {
+ setViewVisibility(mTransitioningViews, View.INVISIBLE);
+ }
+ mActivity.overridePendingTransition(0, 0);
+ if (!mIsReturning) {
+ mActivity.convertToTranslucent(null, null);
+ Drawable background = getDecor().getBackground();
+ if (background != null) {
+ getWindow().setBackgroundDrawable(null);
+ background = background.mutate();
+ background.setAlpha(0);
+ getWindow().setBackgroundDrawable(background);
+ }
+ } else {
+ mActivity = null; // all done with it now.
}
}
@Override
protected Transition getViewsTransition() {
- if (!mSupportsTransition) {
- return null;
+ if (mIsReturning) {
+ return getWindow().getExitTransition();
+ } else {
+ return getWindow().getEnterTransition();
}
- return getWindow().getEnterTransition();
}
- @Override
protected Transition getSharedElementTransition() {
- if (!mSupportsTransition) {
- return null;
+ if (mIsReturning) {
+ return getWindow().getSharedElementExitTransition();
+ } else {
+ return getWindow().getSharedElementEnterTransition();
}
- 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();
- }
- });
+ protected void onTakeSharedElements(Bundle sharedElementState) {
+ setEpicenter();
+ // Remove rejected shared elements
+ ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames);
+ rejectedNames.removeAll(mSharedElementNames);
+ ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames);
+ mListener.handleRejectedSharedElements(rejectedSnapshots);
+ startRejectedAnimations(rejectedSnapshots);
+
+ // Now start shared element transition
+ ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
+ mSharedElementNames);
+ setViewVisibility(mSharedElements, View.VISIBLE);
+ ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState =
+ setSharedElementState(sharedElementState, sharedElementSnapshots);
+ requestLayoutForSharedElements();
+
+ boolean startEnterTransition = allowOverlappingTransitions();
+ boolean startSharedElementTransition = true;
+ Transition transition = beginTransition(startEnterTransition, startSharedElementTransition);
+
+ if (startEnterTransition) {
+ startEnterTransition(transition);
+ }
+
+ setOriginalImageViewState(originalImageViewState);
+
+ if (mResultReceiver != null) {
+ mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null);
}
- super.onStartEnterTransition(transition, enteringViews);
+ mResultReceiver = null; // all done sending messages.
}
- public ArrayList<View> readyEnteringViews() {
- ArrayList<View> enteringViews = new ArrayList<View>();
- getDecor().captureTransitioningViews(enteringViews);
- if (getViewsTransition() != null) {
- setViewVisibility(enteringViews, View.INVISIBLE);
+ private void requestLayoutForSharedElements() {
+ int numSharedElements = mSharedElements.size();
+ for (int i = 0; i < numSharedElements; i++) {
+ mSharedElements.get(i).requestLayout();
}
- return enteringViews;
}
- @Override
- protected void startExitTransition(ArrayList<String> sharedElements) {
- mMakeOpaque = false;
- notifyPrepareRestore();
+ private Transition beginTransition(boolean startEnterTransition,
+ boolean startSharedElementTransition) {
+ Transition sharedElementTransition = null;
+ if (startSharedElementTransition && !mSharedElementNames.isEmpty()) {
+ sharedElementTransition = configureTransition(getSharedElementTransition());
+ }
+ Transition viewsTransition = null;
+ if (startEnterTransition && !mTransitioningViews.isEmpty()) {
+ viewsTransition = configureTransition(getViewsTransition());
+ viewsTransition = addTargets(viewsTransition, mTransitioningViews);
+ }
- if (getDecor().getBackground() == null) {
- ColorDrawable black = new ColorDrawable(0xFF000000);
- getWindow().setBackgroundDrawable(black);
+ Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
+ if (startSharedElementTransition) {
+ if (transition == null) {
+ sharedElementTransitionStarted();
+ } else {
+ transition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionStart(Transition transition) {
+ transition.removeListener(this);
+ sharedElementTransitionStarted();
+ }
+ });
+ }
}
- if (mWasOpaque) {
- mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
- @Override
- public void onTranslucentConversionComplete(boolean drawComplete) {
- fadeOutBackground();
- }
- });
- } else {
- fadeOutBackground();
+ if (transition != null) {
+ TransitionManager.beginDelayedTransition(getDecor(), transition);
+ if (startSharedElementTransition && !mSharedElementNames.isEmpty()) {
+ mSharedElements.get(0).invalidate();
+ } else if (startEnterTransition && !mTransitioningViews.isEmpty()) {
+ mTransitioningViews.get(0).invalidate();
+ }
}
+ return transition;
+ }
- super.startExitTransition(sharedElements);
+ private void sharedElementTransitionStarted() {
+ mSharedElementTransitionStarted = true;
+ if (mIsExitTransitionComplete) {
+ send(MSG_EXIT_TRANSITION_COMPLETE, null);
+ }
}
- 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();
- }
+ private void startEnterTransition(Transition transition) {
+ setViewVisibility(mTransitioningViews, View.VISIBLE);
+ if (!mIsReturning) {
+ Drawable background = getDecor().getBackground();
+ if (background != null) {
+ background = background.mutate();
+ mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 255);
+ mBackgroundAnimator.setDuration(FADE_BACKGROUND_DURATION_MS);
+ mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ makeOpaque();
+ }
+ });
+ mBackgroundAnimator.start();
+ } else if (transition != null) {
+ transition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ transition.removeListener(this);
+ makeOpaque();
+ }
+ });
+ } else {
+ makeOpaque();
}
- });
- animator.setDuration(FADE_BACKGROUND_DURATION_MS);
- animator.start();
+ }
}
- @Override
- protected void onExitTransitionEnd() {
- mExitTransitionComplete = true;
- exitAfterSharedElementTransition();
- super.onExitTransitionEnd();
+ public void stop() {
+ mHasStopped = true;
+ mActivity = null;
+ mIsCanceled = true;
+ mResultReceiver = null;
+ if (mBackgroundAnimator != null) {
+ mBackgroundAnimator.cancel();
+ mBackgroundAnimator = null;
+ }
}
- @Override
- protected void onSharedElementTransitionEnd() {
- mSharedElementTransitionComplete = true;
- if (mBackgroundFadedOut) {
- super.onSharedElementTransitionEnd();
+ private void makeOpaque() {
+ if (!mHasStopped) {
+ mActivity.convertFromTranslucent();
+ mActivity = null;
}
}
- @Override
- protected boolean allowOverlappingTransitions() {
- return getWindow().getAllowEnterTransitionOverlap();
+ private boolean allowOverlappingTransitions() {
+ return mIsReturning ? getWindow().getAllowExitTransitionOverlap()
+ : getWindow().getAllowEnterTransitionOverlap();
}
- private void exitAfterSharedElementTransition() {
- if (mSharedElementTransitionComplete && mExitTransitionComplete && mBackgroundFadedOut) {
- mActivity.finish();
- if (mSupportsTransition) {
- mActivity.overridePendingTransition(0, 0);
+ private void startRejectedAnimations(final ArrayList<View> rejectedSnapshots) {
+ if (rejectedSnapshots == null || rejectedSnapshots.isEmpty()) {
+ return;
+ }
+ ViewGroupOverlay overlay = getDecor().getOverlay();
+ ObjectAnimator animator = null;
+ int numRejected = rejectedSnapshots.size();
+ for (int i = 0; i < numRejected; i++) {
+ View snapshot = rejectedSnapshots.get(i);
+ overlay.add(snapshot);
+ animator = ObjectAnimator.ofFloat(snapshot, View.ALPHA, 1, 0);
+ animator.start();
+ }
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ViewGroupOverlay overlay = getDecor().getOverlay();
+ int numRejected = rejectedSnapshots.size();
+ for (int i = 0; i < numRejected; i++) {
+ overlay.remove(rejectedSnapshots.get(i));
+ }
}
- notifyExitTransitionComplete();
- clearConnections();
+ });
+ }
+
+ protected void onRemoteExitTransitionComplete() {
+ if (!allowOverlappingTransitions()) {
+ boolean startEnterTransition = true;
+ boolean startSharedElementTransition = false;
+ Transition transition = beginTransition(startEnterTransition,
+ startSharedElementTransition);
+ startEnterTransition(transition);
}
}
}
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index d920787..ba1638f 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -15,11 +15,19 @@
*/
package android.app;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Intent;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
import android.transition.Transition;
-import android.util.Pair;
+import android.transition.TransitionManager;
import android.view.View;
-import android.view.Window;
+import android.view.ViewTreeObserver;
import java.util.ArrayList;
@@ -30,142 +38,287 @@ import java.util.ArrayList;
*/
class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
private static final String TAG = "ExitTransitionCoordinator";
+ private static final long MAX_WAIT_MS = 1000;
- /**
- * 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;
+ private Bundle mSharedElementBundle;
- /**
- * Has the shared element transition completed?
- */
- private boolean mSharedElementsComplete;
+ private boolean mExitNotified;
- public ExitTransitionCoordinator(Window window,
- ActivityOptions.ActivityTransitionListener listener) {
- super(window);
- setActivityTransitionListener(listener);
- }
+ private boolean mSharedElementNotified;
- @Override
- protected void onSetResultReceiver() {
- mIsResultReceiverSet = true;
- notifyCompletions();
+ private Activity mActivity;
+
+ private boolean mIsBackgroundReady;
+
+ private boolean mIsCanceled;
+
+ private Handler mHandler;
+
+ private ObjectAnimator mBackgroundAnimator;
+
+ private boolean mIsHidden;
+
+ private boolean mExitTransitionStarted;
+
+ private Bundle mExitSharedElementBundle;
+
+ public ExitTransitionCoordinator(Activity activity, ArrayList<String> names,
+ ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) {
+ super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning),
+ isReturning);
+ mIsBackgroundReady = !isReturning;
+ mActivity = activity;
}
- @Override
- protected void onPrepareRestore() {
- makeTransitioningViewsInvisible();
- setEnteringViews(mTransitioningViews);
- mTransitioningViews = null;
- super.onPrepareRestore();
+ private static SharedElementListener getListener(Activity activity, boolean isReturning) {
+ return isReturning ? activity.mEnterTransitionListener : activity.mExitTransitionListener;
}
@Override
- protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) {
- super.onTakeSharedElements(sharedElementNames, state);
- clearConnections();
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ switch (resultCode) {
+ case MSG_SET_REMOTE_RECEIVER:
+ mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER);
+ if (mIsCanceled) {
+ mResultReceiver.send(MSG_CANCEL, null);
+ mResultReceiver = null;
+ } else {
+ if (mHandler != null) {
+ mHandler.removeMessages(MSG_CANCEL);
+ }
+ notifyComplete();
+ }
+ break;
+ case MSG_HIDE_SHARED_ELEMENTS:
+ if (!mIsCanceled) {
+ hideSharedElements();
+ }
+ break;
+ case MSG_START_EXIT_TRANSITION:
+ startExit();
+ break;
+ case MSG_ACTIVITY_STOPPED:
+ setViewVisibility(mTransitioningViews, View.VISIBLE);
+ setViewVisibility(mSharedElements, View.VISIBLE);
+ mIsHidden = true;
+ break;
+ case MSG_SHARED_ELEMENT_DESTINATION:
+ mExitSharedElementBundle = resultData;
+ if (mExitTransitionStarted) {
+ startSharedElementExit();
+ }
+ break;
+ }
}
- @Override
- protected void onActivityStopped() {
- if (getViewsTransition() != null) {
- setViewVisibility(mTransitioningViews, View.VISIBLE);
+ private void startSharedElementExit() {
+ if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) {
+ Transition transition = getSharedElementExitTransition();
+ TransitionManager.beginDelayedTransition(getDecor(), transition);
+ ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
+ mSharedElementNames);
+ setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
}
- super.onActivityStopped();
}
- @Override
- protected void sharedElementTransitionComplete(Bundle bundle) {
- mSharedElements = bundle;
- mSharedElementsComplete = true;
- notifyCompletions();
+ private void hideSharedElements() {
+ setViewVisibility(mSharedElements, View.INVISIBLE);
+ finishIfNecessary();
}
- @Override
- protected void onExitTransitionEnd() {
- mExitComplete = true;
- notifyCompletions();
- super.onExitTransitionEnd();
+ public void startExit() {
+ beginTransitions();
+ setViewVisibility(mTransitioningViews, View.INVISIBLE);
}
- private void notifyCompletions() {
- if (mIsResultReceiverSet && mSharedElementsComplete) {
- if (mSharedElements != null) {
- notifySharedElementTransitionComplete(mSharedElements);
- mSharedElements = null;
+ public void startExit(int resultCode, Intent data) {
+ mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ mIsCanceled = true;
+ mActivity.finish();
+ mActivity = null;
}
- if (mExitComplete) {
- notifyExitTransitionComplete();
+ };
+ mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
+ if (getDecor().getBackground() == null) {
+ ColorDrawable black = new ColorDrawable(0xFF000000);
+ black.setAlpha(0);
+ getWindow().setBackgroundDrawable(black);
+ black.setAlpha(255);
+ }
+ ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this,
+ mAllSharedElementNames, resultCode, data);
+ mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
+ @Override
+ public void onTranslucentConversionComplete(boolean drawComplete) {
+ if (!mIsCanceled) {
+ fadeOutBackground();
+ }
}
+ }, options);
+ Transition sharedElementTransition = mSharedElements.isEmpty()
+ ? null : getSharedElementTransition();
+ if (sharedElementTransition == null) {
+ sharedElementTransitionComplete();
+ }
+ Transition transition = mergeTransitions(sharedElementTransition, getExitTransition());
+ if (transition == null) {
+ mExitTransitionStarted = true;
+ } else {
+ TransitionManager.beginDelayedTransition(getDecor(), transition);
+ setViewVisibility(mTransitioningViews, View.INVISIBLE);
+ getDecor().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
+ mExitTransitionStarted = true;
+ if (mExitSharedElementBundle != null) {
+ startSharedElementExit();
+ }
+ notifyComplete();
+ return true;
+ }
+ });
}
}
- @Override
- public void startExit() {
- if (!mExitStarted) {
- mExitStarted = true;
- setSharedElements();
- startExitTransition(getSharedElementNames());
+ private void fadeOutBackground() {
+ if (mBackgroundAnimator == null) {
+ Drawable background = getDecor().getBackground();
+ mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0);
+ mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBackgroundAnimator = null;
+ if (!mIsCanceled) {
+ mIsBackgroundReady = true;
+ notifyComplete();
+ }
+ }
+ });
+ mBackgroundAnimator.setDuration(FADE_BACKGROUND_DURATION_MS);
+ mBackgroundAnimator.start();
}
}
- @Override
- protected Transition getViewsTransition() {
- if (!getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
- return null;
+ private Transition getExitTransition() {
+ Transition viewsTransition = null;
+ if (!mTransitioningViews.isEmpty()) {
+ viewsTransition = configureTransition(getViewsTransition());
+ }
+ if (viewsTransition == null) {
+ exitTransitionComplete();
+ } else {
+ viewsTransition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ exitTransitionComplete();
+ if (mIsHidden) {
+ setViewVisibility(mTransitioningViews, View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onTransitionCancel(Transition transition) {
+ super.onTransitionCancel(transition);
+ }
+ });
}
- return getWindow().getExitTransition();
+ return viewsTransition;
}
- @Override
- protected Transition getSharedElementTransition() {
- if (!getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
- return null;
+ private Transition getSharedElementExitTransition() {
+ Transition sharedElementTransition = null;
+ if (!mSharedElements.isEmpty()) {
+ sharedElementTransition = configureTransition(getSharedElementTransition());
}
- return getWindow().getSharedElementExitTransition();
+ if (sharedElementTransition == null) {
+ sharedElementTransitionComplete();
+ } else {
+ sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ sharedElementTransitionComplete();
+ if (mIsHidden) {
+ setViewVisibility(mSharedElements, View.VISIBLE);
+ }
+ }
+ });
+ mSharedElements.get(0).invalidate();
+ }
+ return sharedElementTransition;
}
- private void makeTransitioningViewsInvisible() {
- if (getViewsTransition() != null) {
- setViewVisibility(mTransitioningViews, View.INVISIBLE);
+ private void beginTransitions() {
+ Transition sharedElementTransition = getSharedElementExitTransition();
+ Transition viewsTransition = getExitTransition();
+
+ Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
+ mExitTransitionStarted = true;
+ if (transition != null) {
+ TransitionManager.beginDelayedTransition(getDecor(), transition);
}
}
- @Override
- protected void onStartExitTransition(ArrayList<View> exitingViews) {
- mTransitioningViews = new ArrayList<View>();
- if (exitingViews != null) {
- mTransitioningViews.addAll(exitingViews);
+ private void exitTransitionComplete() {
+ mExitComplete = true;
+ notifyComplete();
+ }
+
+ protected boolean isReadyToNotify() {
+ return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady
+ && mExitTransitionStarted;
+ }
+
+ private void sharedElementTransitionComplete() {
+ mSharedElementBundle = captureSharedElementState();
+ notifyComplete();
+ }
+
+ protected void notifyComplete() {
+ if (isReadyToNotify()) {
+ if (!mSharedElementNotified) {
+ mSharedElementNotified = true;
+ mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
+ }
+ if (!mExitNotified && mExitComplete) {
+ mExitNotified = true;
+ mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
+ mResultReceiver = null; // done talking
+ finishIfNecessary();
+ }
+ }
+ }
+
+ private void finishIfNecessary() {
+ if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty()
+ || mSharedElements.get(0).getVisibility() == View.INVISIBLE)) {
+ mActivity.finish();
+ mActivity.overridePendingTransition(0, 0);
+ mActivity = null;
+ }
+ if (!mIsReturning && mExitNotified) {
+ mActivity = null; // don't need it anymore
}
- mTransitioningViews.addAll(getSharedElements());
}
@Override
- protected boolean allowOverlappingTransitions() {
- return getWindow().getAllowExitTransitionOverlap();
+ protected Transition getViewsTransition() {
+ if (mIsReturning) {
+ return getWindow().getEnterTransition();
+ } else {
+ return getWindow().getExitTransition();
+ }
+ }
+
+ protected Transition getSharedElementTransition() {
+ if (mIsReturning) {
+ return getWindow().getSharedElementEnterTransition();
+ } else {
+ return getWindow().getSharedElementExitTransition();
+ }
}
}
diff --git a/core/java/android/app/FragmentBreadCrumbs.java b/core/java/android/app/FragmentBreadCrumbs.java
index e4de7af..ab0fc66 100644
--- a/core/java/android/app/FragmentBreadCrumbs.java
+++ b/core/java/android/app/FragmentBreadCrumbs.java
@@ -37,7 +37,10 @@ import android.widget.TextView;
*
* <p>The default style for this view is
* {@link android.R.style#Widget_FragmentBreadCrumbs}.
+ *
+ * @deprecated This widget is no longer supported.
*/
+@Deprecated
public class FragmentBreadCrumbs extends ViewGroup
implements FragmentManager.OnBackStackChangedListener {
Activity mActivity;
@@ -88,6 +91,9 @@ public class FragmentBreadCrumbs extends ViewGroup
this(context, attrs, defStyleAttr, 0);
}
+ /**
+ * @hide
+ */
public FragmentBreadCrumbs(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 2e9cdf3b7..bf2d7e5 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -45,6 +45,7 @@ import android.os.IInterface;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.StrictMode;
import android.service.voice.IVoiceInteractionSession;
@@ -104,13 +105,14 @@ public interface IActivityManager extends IInterface {
public void activityResumed(IBinder token) throws RemoteException;
public void activityIdle(IBinder token, Configuration config,
boolean stopProfiling) throws RemoteException;
- public void activityPaused(IBinder token) throws RemoteException;
+ public void activityPaused(IBinder token, PersistableBundle persistentState) throws RemoteException;
public void activityStopped(IBinder token, Bundle state,
- Bitmap thumbnail, CharSequence description) throws RemoteException;
+ PersistableBundle persistentState, CharSequence description) throws RemoteException;
public void activitySlept(IBinder token) throws RemoteException;
public void activityDestroyed(IBinder token) throws RemoteException;
public String getCallingPackage(IBinder token) throws RemoteException;
public ComponentName getCallingActivity(IBinder token) throws RemoteException;
+ public List<IAppTask> getAppTasks() throws RemoteException;
public List<RunningTaskInfo> getTasks(int maxNum, int flags) throws RemoteException;
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
int flags, int userId) throws RemoteException;
@@ -174,7 +176,8 @@ public interface IActivityManager extends IInterface {
public boolean startInstrumentation(ComponentName className, String profileFile,
int flags, Bundle arguments, IInstrumentationWatcher watcher,
- IUiAutomationConnection connection, int userId) throws RemoteException;
+ IUiAutomationConnection connection, int userId,
+ String abiOverride) throws RemoteException;
public void finishInstrumentation(IApplicationThread target,
int resultCode, Bundle results) throws RemoteException;
@@ -209,14 +212,16 @@ public interface IActivityManager extends IInterface {
public int checkPermission(String permission, int pid, int uid)
throws RemoteException;
- public int checkUriPermission(Uri uri, int pid, int uid, int mode)
+ public int checkUriPermission(Uri uri, int pid, int uid, int mode, int userId)
+ throws RemoteException;
+ public void grantUriPermission(IApplicationThread caller, String targetPkg, Uri uri,
+ int mode, int userId) throws RemoteException;
+ public void revokeUriPermission(IApplicationThread caller, Uri uri, int mode, int userId)
+ throws RemoteException;
+ public void takePersistableUriPermission(Uri uri, int modeFlags, int userId)
+ throws RemoteException;
+ public void releasePersistableUriPermission(Uri uri, int modeFlags, int userId)
throws RemoteException;
- public void grantUriPermission(IApplicationThread caller, String targetPkg,
- Uri uri, int mode) throws RemoteException;
- public void revokeUriPermission(IApplicationThread caller, Uri uri,
- int mode) throws RemoteException;
- public void takePersistableUriPermission(Uri uri, int modeFlags) throws RemoteException;
- public void releasePersistableUriPermission(Uri uri, int modeFlags) throws RemoteException;
public ParceledListSlice<UriPermission> getPersistedUriPermissions(
String packageName, boolean incoming) throws RemoteException;
@@ -308,8 +313,9 @@ public interface IActivityManager extends IInterface {
public void finishHeavyWeightApp() throws RemoteException;
public boolean convertFromTranslucent(IBinder token) throws RemoteException;
- public boolean convertToTranslucent(IBinder token) throws RemoteException;
+ public boolean convertToTranslucent(IBinder token, ActivityOptions options) throws RemoteException;
public void notifyActivityDrawn(IBinder token) throws RemoteException;
+ public ActivityOptions getActivityOptions(IBinder token) throws RemoteException;
public void setImmersive(IBinder token, boolean immersive) throws RemoteException;
public boolean isImmersive(IBinder token) throws RemoteException;
@@ -322,12 +328,12 @@ public interface IActivityManager extends IInterface {
public IBinder newUriPermissionOwner(String name) throws RemoteException;
public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg,
- Uri uri, int mode) throws RemoteException;
+ Uri uri, int mode, int userId) throws RemoteException;
public void revokeUriPermissionFromOwner(IBinder owner, Uri uri,
- int mode) throws RemoteException;
+ int mode, int userId) throws RemoteException;
- public int checkGrantUriPermission(int callingUid, String targetPkg,
- Uri uri, int modeFlags) throws RemoteException;
+ public int checkGrantUriPermission(int callingUid, String targetPkg, Uri uri,
+ int modeFlags, int userId) throws RemoteException;
// Cause the specified process to dump the specified heap.
public boolean dumpHeap(String process, int userId, boolean managed, String path,
@@ -435,7 +441,7 @@ public interface IActivityManager extends IInterface {
public boolean isInLockTaskMode() throws RemoteException;
/** @hide */
- public void setRecentsActivityValues(IBinder token, ActivityManager.RecentsActivityValues values)
+ public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values)
throws RemoteException;
/*
@@ -734,6 +740,8 @@ public interface IActivityManager extends IInterface {
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_RECENTS_ACTIVITY_VALUES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+217;
+ int SET_TASK_DESCRIPTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+217;
int START_VOICE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+218;
+ int GET_ACTIVITY_OPTIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+219;
+ int GET_APP_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+220;
}
diff --git a/core/java/android/app/IAppTask.aidl b/core/java/android/app/IAppTask.aidl
new file mode 100644
index 0000000..268b4dd
--- /dev/null
+++ b/core/java/android/app/IAppTask.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.app;
+
+import android.app.ActivityManager;
+
+/** @hide */
+interface IAppTask {
+ void finishAndRemoveTask();
+ ActivityManager.RecentTaskInfo getTaskInfo();
+}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index fefba8a..d0df7c3 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -25,9 +25,11 @@ import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Debug;
import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.IInterface;
@@ -58,10 +60,9 @@ public interface IApplicationThread extends IInterface {
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,
- Bundle resumeArgs)
+ PersistableBundle persistentState, List<ResultInfo> pendingResults,
+ List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+ String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler)
throws RemoteException;
void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, int configChanges,
@@ -105,7 +106,7 @@ public interface IApplicationThread extends IInterface {
void updateTimeZone() throws RemoteException;
void clearDnsCache() throws RemoteException;
void setHttpProxy(String proxy, String port, String exclList,
- String pacFileUrl) throws RemoteException;
+ Uri pacFileUrl) throws RemoteException;
void processInBackground() throws RemoteException;
void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args)
throws RemoteException;
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index b917263..a3ffc00 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -21,6 +21,7 @@ import android.app.ITransientNotification;
import android.app.Notification;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.ParceledListSlice;
import android.net.Uri;
import android.service.notification.Condition;
import android.service.notification.IConditionListener;
@@ -43,6 +44,8 @@ interface INotificationManager
void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
boolean areNotificationsEnabledForPackage(String pkg, int uid);
+ // TODO: Remove this when callers have been migrated to the equivalent
+ // INotificationListener method.
StatusBarNotification[] getActiveNotifications(String callingPkg);
StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count);
@@ -52,8 +55,7 @@ interface INotificationManager
void cancelNotificationFromListener(in INotificationListener token, String pkg, String tag, int id);
void cancelNotificationsFromListener(in INotificationListener token, in String[] keys);
- StatusBarNotification[] getActiveNotificationsFromListener(in INotificationListener token, in String[] keys);
- String[] getActiveNotificationKeysFromListener(in INotificationListener token);
+ ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token);
ZenModeConfig getZenModeConfig();
boolean setZenModeConfig(in ZenModeConfig config);
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 347de97..8ab9ac3 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -43,4 +43,5 @@ interface IUiAutomationConnection {
WindowContentFrameStats getWindowContentFrameStats(int windowId);
void clearWindowAnimationFrameStats();
WindowAnimationFrameStats getWindowAnimationFrameStats();
+ void executeShellCommand(String command, in ParcelFileDescriptor fd);
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index e58ccb8..b78b9c9 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -30,6 +30,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.MessageQueue;
import android.os.PerformanceCollector;
+import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -1035,10 +1036,10 @@ public class Instrumentation {
IllegalAccessException {
Activity activity = (Activity)clazz.newInstance();
ActivityThread aThread = null;
- activity.attach(context, aThread, this, token, application, intent,
+ activity.attach(context, aThread, this, token, 0, application, intent,
info, title, parent, id,
(Activity.NonConfigurationInstances)lastNonConfigurationInstance,
- new Configuration());
+ new Configuration(), null);
return activity;
}
@@ -1061,15 +1062,7 @@ public class Instrumentation {
return (Activity)cl.loadClass(className).newInstance();
}
- /**
- * Perform calling of an activity's {@link Activity#onCreate}
- * method. The default implementation simply calls through to that method.
- *
- * @param activity The activity being created.
- * @param icicle The previously frozen state (or null) to pass through to
- * onCreate().
- */
- public void callActivityOnCreate(Activity activity, Bundle icicle) {
+ private void prePerformCreate(Activity activity) {
if (mWaitingActivities != null) {
synchronized (mSync) {
final int N = mWaitingActivities.size();
@@ -1083,9 +1076,9 @@ public class Instrumentation {
}
}
}
-
- activity.performCreate(icicle);
-
+ }
+
+ private void postPerformCreate(Activity activity) {
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -1096,6 +1089,33 @@ public class Instrumentation {
}
}
}
+
+ /**
+ * Perform calling of an activity's {@link Activity#onCreate}
+ * method. The default implementation simply calls through to that method.
+ *
+ * @param activity The activity being created.
+ * @param icicle The previously frozen state (or null) to pass through to onCreate().
+ */
+ public void callActivityOnCreate(Activity activity, Bundle icicle) {
+ prePerformCreate(activity);
+ activity.performCreate(icicle);
+ postPerformCreate(activity);
+ }
+
+ /**
+ * Perform calling of an activity's {@link Activity#onCreate}
+ * method. The default implementation simply calls through to that method.
+ * @param activity The activity being created.
+ * @param icicle The previously frozen state (or null) to pass through to
+ * @param persistentState The previously persisted state (or null)
+ */
+ public void callActivityOnCreate(Activity activity, Bundle icicle,
+ PersistableBundle persistentState) {
+ prePerformCreate(activity);
+ activity.performCreate(icicle, persistentState);
+ postPerformCreate(activity);
+ }
public void callActivityOnDestroy(Activity activity) {
// TODO: the following block causes intermittent hangs when using startActivity
@@ -1130,7 +1150,7 @@ public class Instrumentation {
/**
* Perform calling of an activity's {@link Activity#onRestoreInstanceState}
* method. The default implementation simply calls through to that method.
- *
+ *
* @param activity The activity being restored.
* @param savedInstanceState The previously saved state being restored.
*/
@@ -1139,9 +1159,22 @@ public class Instrumentation {
}
/**
+ * Perform calling of an activity's {@link Activity#onRestoreInstanceState}
+ * method. The default implementation simply calls through to that method.
+ *
+ * @param activity The activity being restored.
+ * @param savedInstanceState The previously saved state being restored.
+ * @param persistentState The previously persisted state (or null)
+ */
+ public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState,
+ PersistableBundle persistentState) {
+ activity.performRestoreInstanceState(savedInstanceState, persistentState);
+ }
+
+ /**
* Perform calling of an activity's {@link Activity#onPostCreate} method.
* The default implementation simply calls through to that method.
- *
+ *
* @param activity The activity being created.
* @param icicle The previously frozen state (or null) to pass through to
* onPostCreate().
@@ -1151,6 +1184,19 @@ public class Instrumentation {
}
/**
+ * Perform calling of an activity's {@link Activity#onPostCreate} method.
+ * The default implementation simply calls through to that method.
+ *
+ * @param activity The activity being created.
+ * @param icicle The previously frozen state (or null) to pass through to
+ * onPostCreate().
+ */
+ public void callActivityOnPostCreate(Activity activity, Bundle icicle,
+ PersistableBundle persistentState) {
+ activity.onPostCreate(icicle, persistentState);
+ }
+
+ /**
* Perform calling of an activity's {@link Activity#onNewIntent}
* method. The default implementation simply calls through to that method.
*
@@ -1215,7 +1261,7 @@ public class Instrumentation {
/**
* Perform calling of an activity's {@link Activity#onSaveInstanceState}
* method. The default implementation simply calls through to that method.
- *
+ *
* @param activity The activity being saved.
* @param outState The bundle to pass to the call.
*/
@@ -1224,6 +1270,18 @@ public class Instrumentation {
}
/**
+ * Perform calling of an activity's {@link Activity#onSaveInstanceState}
+ * method. The default implementation simply calls through to that method.
+ * @param activity The activity being saved.
+ * @param outState The bundle to pass to the call.
+ * @param outPersistentState The persistent bundle to pass to the call.
+ */
+ public void callActivityOnSaveInstanceState(Activity activity, Bundle outState,
+ PersistableBundle outPersistentState) {
+ activity.performSaveInstanceState(outState, outPersistentState);
+ }
+
+ /**
* Perform calling of an activity's {@link Activity#onPause} method. The
* default implementation simply calls through to that method.
*
@@ -1428,7 +1486,7 @@ public class Instrumentation {
}
/**
- * Like {@link #execStartActivity},
+ * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int, Bundle)},
* 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 +1500,7 @@ public class Instrumentation {
}
/**
- * Like {@link #execStartActivity},
+ * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int, Bundle)},
* 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,7 +1603,8 @@ public class Instrumentation {
}
/**
- * Like {@link #execStartActivity}, but for starting as a particular user.
+ * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int, Bundle)},
+ * 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
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index aab6ed8..db91742a 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -44,8 +44,8 @@ public class KeyguardManager {
* you to disable / reenable the keyguard.
*/
public class KeyguardLock {
- private IBinder mToken = new Binder();
- private String mTag;
+ private final IBinder mToken = new Binder();
+ private final String mTag;
KeyguardLock(String tag) {
mTag = tag;
diff --git a/core/java/android/app/LauncherActivity.java b/core/java/android/app/LauncherActivity.java
index 96c7246..9ec7f41 100644
--- a/core/java/android/app/LauncherActivity.java
+++ b/core/java/android/app/LauncherActivity.java
@@ -340,9 +340,11 @@ public abstract class LauncherActivity extends ListActivity {
super.onCreate(icicle);
mPackageManager = getPackageManager();
-
- requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
- setProgressBarIndeterminateVisibility(true);
+
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+ setProgressBarIndeterminateVisibility(true);
+ }
onSetContentView();
mIconResizer = new IconResizer();
@@ -357,7 +359,9 @@ public abstract class LauncherActivity extends ListActivity {
updateAlertTitle();
updateButtonText();
- setProgressBarIndeterminateVisibility(false);
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ setProgressBarIndeterminateVisibility(false);
+ }
}
private void updateAlertTitle() {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index bba6caf..8dba1dc 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -16,9 +16,6 @@
package android.app;
-import com.android.internal.R;
-import com.android.internal.util.NotificationColorUtil;
-
import android.annotation.IntDef;
import android.content.Context;
import android.content.Intent;
@@ -26,6 +23,7 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.PorterDuff;
import android.media.AudioManager;
+import android.media.session.MediaSessionToken;
import android.net.Uri;
import android.os.BadParcelableException;
import android.os.Build;
@@ -37,14 +35,21 @@ import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
+import android.view.Gravity;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.RemoteViews;
+import com.android.internal.R;
+import com.android.internal.util.NotificationColorUtil;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.NumberFormat;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
/**
* A class that represents how a persistent notification is to be presented to
@@ -134,7 +139,7 @@ public class Notification implements Parcelable
* leave it at its default value of 0.
*
* @see android.widget.ImageView#setImageLevel
- * @see android.graphics.drawable#setLevel
+ * @see android.graphics.drawable.Drawable#setLevel
*/
public int iconLevel;
@@ -370,6 +375,14 @@ public class Notification implements Parcelable
*/
public static final int FLAG_LOCAL_ONLY = 0x00000100;
+ /**
+ * Bit to be bitswise-ored into the {@link #flags} field that should be
+ * set if this notification is the group summary for a group of notifications.
+ * Grouped notifications may display in a cluster or stack on devices which
+ * support such rendering. Requires a group key also be set using {@link Builder#setGroup}.
+ */
+ public static final int FLAG_GROUP_SUMMARY = 0x00000200;
+
public int flags;
/** @hide */
@@ -539,6 +552,34 @@ public class Notification implements Parcelable
*/
public String category;
+ private String mGroupKey;
+
+ /**
+ * Get the key used to group this notification into a cluster or stack
+ * with other notifications on devices which support such rendering.
+ */
+ public String getGroup() {
+ return mGroupKey;
+ }
+
+ private String mSortKey;
+
+ /**
+ * Get a sort key that orders this notification among other notifications from the
+ * same package. This can be useful if an external sort was already applied and an app
+ * would like to preserve this. Notifications will be sorted lexicographically using this
+ * value, although providing different priorities in addition to providing sort key may
+ * cause this value to be ignored.
+ *
+ * <p>This sort key can also be used to order members of a notification group. See
+ * {@link Builder#setGroup}.
+ *
+ * @see String#compareTo(String)
+ */
+ public String getSortKey() {
+ return mSortKey;
+ }
+
/**
* Additional semantic data to be carried around with this Notification.
* <p>
@@ -649,6 +690,11 @@ 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";
+
+ /**
+ * {@link #extras} key: A string representing the name of the specific
+ * {@link android.app.Notification.Style} used to create this notification.
+ */
public static final String EXTRA_TEMPLATE = "android.template";
/**
@@ -659,8 +705,8 @@ public class Notification implements Parcelable
/**
* @hide
- * Extra added by NotificationManagerService to indicate whether a NotificationScorer
- * modified the Notifications's score.
+ * Extra added by NotificationManagerService to indicate whether
+ * the Notifications's score has been modified.
*/
public static final String EXTRA_SCORE_MODIFIED = "android.scoreModified";
@@ -678,6 +724,24 @@ public class Notification implements Parcelable
public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup";
/**
+ * {@link #extras} key: A
+ * {@link android.content.ContentUris content URI} pointing to an image that can be displayed
+ * in the background when the notification is selected. The URI must point to an image stream
+ * suitable for passing into
+ * {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
+ * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider
+ * URI used for this purpose must require no permissions to read the image data.
+ */
+ public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
+
+ /**
+ * {@link #extras} key: A
+ * {@link android.media.session.MediaSessionToken} associated with a
+ * {@link android.app.Notification.MediaStyle} notification.
+ */
+ public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
+
+ /**
* Value for {@link #EXTRA_AS_HEADS_UP}.
* @hide
*/
@@ -700,48 +764,180 @@ public class Notification implements Parcelable
* It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
* selected by the user.
* <p>
- * Apps should use {@link Builder#addAction(int, CharSequence, PendingIntent)} to create and
- * attach actions.
+ * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}
+ * or {@link Notification.Builder#addAction(Notification.Action)}
+ * to attach actions.
*/
public static class Action implements Parcelable {
+ private final Bundle mExtras;
+ private final RemoteInput[] mRemoteInputs;
+
/**
* Small icon representing the action.
*/
public int icon;
+
/**
* Title of the action.
*/
public CharSequence title;
+
/**
* Intent to send when the user invokes this action. May be null, in which case the action
* may be rendered in a disabled presentation by the system UI.
*/
public PendingIntent actionIntent;
-
- private Action() { }
+
private Action(Parcel in) {
icon = in.readInt();
title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
if (in.readInt() == 1) {
actionIntent = PendingIntent.CREATOR.createFromParcel(in);
}
+ mExtras = in.readBundle();
+ mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
}
+
/**
- * Use {@link Builder#addAction(int, CharSequence, PendingIntent)}.
+ * Use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)}.
*/
public Action(int icon, CharSequence title, PendingIntent intent) {
+ this(icon, title, intent, new Bundle(), null);
+ }
+
+ private Action(int icon, CharSequence title, PendingIntent intent, Bundle extras,
+ RemoteInput[] remoteInputs) {
this.icon = icon;
this.title = title;
this.actionIntent = intent;
+ this.mExtras = extras != null ? extras : new Bundle();
+ this.mRemoteInputs = remoteInputs;
+ }
+
+ /**
+ * Get additional metadata carried around with this Action.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Get the list of inputs to be collected from the user when this action is sent.
+ * May return null if no remote inputs were added.
+ */
+ public RemoteInput[] getRemoteInputs() {
+ return mRemoteInputs;
+ }
+
+ /**
+ * Builder class for {@link Action} objects.
+ */
+ public static final class Builder {
+ private final int mIcon;
+ private final CharSequence mTitle;
+ private final PendingIntent mIntent;
+ private final Bundle mExtras;
+ private ArrayList<RemoteInput> mRemoteInputs;
+
+ /**
+ * Construct a new builder for {@link Action} object.
+ * @param icon icon to show for this action
+ * @param title the title of the action
+ * @param intent the {@link PendingIntent} to fire when users trigger this action
+ */
+ public Builder(int icon, CharSequence title, PendingIntent intent) {
+ this(icon, title, intent, new Bundle(), null);
+ }
+
+ /**
+ * Construct a new builder for {@link Action} object using the fields from an
+ * {@link Action}.
+ * @param action the action to read fields from.
+ */
+ public Builder(Action action) {
+ this(action.icon, action.title, action.actionIntent, new Bundle(action.mExtras),
+ action.getRemoteInputs());
+ }
+
+ private Builder(int icon, CharSequence title, PendingIntent intent, Bundle extras,
+ RemoteInput[] remoteInputs) {
+ mIcon = icon;
+ mTitle = title;
+ mIntent = intent;
+ mExtras = extras;
+ if (remoteInputs != null) {
+ mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length);
+ Collections.addAll(mRemoteInputs, remoteInputs);
+ }
+ }
+
+ /**
+ * Merge additional metadata into this builder.
+ *
+ * <p>Values within the Bundle will replace existing extras values in this Builder.
+ *
+ * @see Notification.Action#extras
+ */
+ public Builder addExtras(Bundle extras) {
+ if (extras != null) {
+ mExtras.putAll(extras);
+ }
+ return this;
+ }
+
+ /**
+ * Get the metadata Bundle used by this Builder.
+ *
+ * <p>The returned Bundle is shared with this Builder.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Add an input to be collected from the user when this action is sent.
+ * Response values can be retrieved from the fired intent by using the
+ * {@link RemoteInput#getResultsFromIntent} function.
+ * @param remoteInput a {@link RemoteInput} to add to the action
+ * @return this object for method chaining
+ */
+ public Builder addRemoteInput(RemoteInput remoteInput) {
+ if (mRemoteInputs == null) {
+ mRemoteInputs = new ArrayList<RemoteInput>();
+ }
+ mRemoteInputs.add(remoteInput);
+ return this;
+ }
+
+ /**
+ * Apply an extender to this action builder. Extenders may be used to add
+ * metadata or change options on this builder.
+ */
+ public Builder extend(Extender extender) {
+ extender.extend(this);
+ return this;
+ }
+
+ /**
+ * Combine all of the options that have been set and return a new {@link Action}
+ * object.
+ * @return the built action
+ */
+ public Action build() {
+ RemoteInput[] remoteInputs = mRemoteInputs != null
+ ? mRemoteInputs.toArray(new RemoteInput[mRemoteInputs.size()]) : null;
+ return new Action(mIcon, mTitle, mIntent, mExtras, remoteInputs);
+ }
}
@Override
public Action clone() {
return new Action(
- this.icon,
- this.title,
- this.actionIntent // safe to alias
- );
+ icon,
+ title,
+ actionIntent, // safe to alias
+ new Bundle(mExtras),
+ getRemoteInputs());
}
@Override
public int describeContents() {
@@ -757,9 +953,11 @@ public class Notification implements Parcelable
} else {
out.writeInt(0);
}
+ out.writeBundle(mExtras);
+ out.writeTypedArray(mRemoteInputs, flags);
}
- public static final Parcelable.Creator<Action> CREATOR
- = new Parcelable.Creator<Action>() {
+ public static final Parcelable.Creator<Action> CREATOR =
+ new Parcelable.Creator<Action>() {
public Action createFromParcel(Parcel in) {
return new Action(in);
}
@@ -767,6 +965,120 @@ public class Notification implements Parcelable
return new Action[size];
}
};
+
+ /**
+ * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
+ * metadata or change options on an action builder.
+ */
+ public interface Extender {
+ /**
+ * Apply this extender to a notification action builder.
+ * @param builder the builder to be modified.
+ * @return the build object for chaining.
+ */
+ public Builder extend(Builder builder);
+ }
+
+ /**
+ * Wearable extender for notification actions. To add extensions to an action,
+ * create a new {@link android.app.Notification.Action.WearableExtender} object using
+ * the {@code WearableExtender()} constructor and apply it to a
+ * {@link android.app.Notification.Action.Builder} using
+ * {@link android.app.Notification.Action.Builder#extend}.
+ *
+ * <pre class="prettyprint">
+ * Notification.Action action = new Notification.Action.Builder(
+ * R.drawable.archive_all, "Archive all", actionIntent)
+ * .extend(new Notification.Action.WearableExtender()
+ * .setAvailableOffline(false))
+ * .build();</pre>
+ */
+ public static final class WearableExtender implements Extender {
+ /** Notification action extra which contains wearable extensions */
+ private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
+
+ private static final String KEY_FLAGS = "flags";
+
+ // Flags bitwise-ored to mFlags
+ private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
+
+ // Default value for flags integer
+ private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
+
+ private int mFlags = DEFAULT_FLAGS;
+
+ /**
+ * Create a {@link android.app.Notification.Action.WearableExtender} with default
+ * options.
+ */
+ public WearableExtender() {
+ }
+
+ /**
+ * Create a {@link android.app.Notification.Action.WearableExtender} by reading
+ * wearable options present in an existing notification action.
+ * @param action the notification action to inspect.
+ */
+ public WearableExtender(Action action) {
+ Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
+ if (wearableBundle != null) {
+ mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
+ }
+ }
+
+ /**
+ * Apply wearable extensions to a notification action that is being built. This is
+ * typically called by the {@link android.app.Notification.Action.Builder#extend}
+ * method of {@link android.app.Notification.Action.Builder}.
+ */
+ @Override
+ public Action.Builder extend(Action.Builder builder) {
+ Bundle wearableBundle = new Bundle();
+
+ if (mFlags != DEFAULT_FLAGS) {
+ wearableBundle.putInt(KEY_FLAGS, mFlags);
+ }
+
+ builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
+ return builder;
+ }
+
+ @Override
+ public WearableExtender clone() {
+ WearableExtender that = new WearableExtender();
+ that.mFlags = this.mFlags;
+ return that;
+ }
+
+ /**
+ * Set whether this action is available when the wearable device is not connected to
+ * a companion device. The user can still trigger this action when the wearable device is
+ * offline, but a visual hint will indicate that the action may not be available.
+ * Defaults to true.
+ */
+ public WearableExtender setAvailableOffline(boolean availableOffline) {
+ setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
+ return this;
+ }
+
+ /**
+ * Get whether this action is available when the wearable device is not connected to
+ * a companion device. The user can still trigger this action when the wearable device is
+ * offline, but a visual hint will indicate that the action may not be available.
+ * Defaults to true.
+ */
+ public boolean isAvailableOffline() {
+ return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
+ }
+
+ private void setFlag(int mask, boolean value) {
+ if (value) {
+ mFlags |= mask;
+ } else {
+ mFlags &= ~mask;
+ }
+ }
+ }
}
/**
@@ -876,6 +1188,10 @@ public class Notification implements Parcelable
category = parcel.readString();
+ mGroupKey = parcel.readString();
+
+ mSortKey = parcel.readString();
+
extras = parcel.readBundle(); // may be null
actions = parcel.createTypedArray(Action.CREATOR); // may be null
@@ -953,6 +1269,10 @@ public class Notification implements Parcelable
that.category = this.category;
+ that.mGroupKey = this.mGroupKey;
+
+ that.mSortKey = this.mSortKey;
+
if (this.extras != null) {
try {
that.extras = new Bundle(this.extras);
@@ -1104,6 +1424,10 @@ public class Notification implements Parcelable
parcel.writeString(category);
+ parcel.writeString(mGroupKey);
+
+ parcel.writeString(mSortKey);
+
parcel.writeBundle(extras); // null ok
parcel.writeTypedArray(actions, 0); // null ok
@@ -1241,7 +1565,18 @@ public class Notification implements Parcelable
sb.append(" flags=0x");
sb.append(Integer.toHexString(this.flags));
sb.append(String.format(" color=0x%08x", this.color));
- sb.append(" category="); sb.append(this.category);
+ if (this.category != null) {
+ sb.append(" category=");
+ sb.append(this.category);
+ }
+ if (this.mGroupKey != null) {
+ sb.append(" groupKey=");
+ sb.append(this.mGroupKey);
+ }
+ if (this.mSortKey != null) {
+ sb.append(" sortKey=");
+ sb.append(this.mSortKey);
+ }
if (actions != null) {
sb.append(" ");
sb.append(actions.length);
@@ -1324,6 +1659,8 @@ public class Notification implements Parcelable
private int mProgress;
private boolean mProgressIndeterminate;
private String mCategory;
+ private String mGroupKey;
+ private String mSortKey;
private Bundle mExtras;
private int mPriority;
private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
@@ -1334,7 +1671,6 @@ public class Notification implements Parcelable
private Notification mPublicVersion = null;
private final NotificationColorUtil mColorUtil;
private ArrayList<String> mPeople;
- private boolean mPreQuantum;
private int mColor = COLOR_DEFAULT;
/**
@@ -1357,6 +1693,15 @@ public class Notification implements Parcelable
* object.
*/
public Builder(Context context) {
+ /*
+ * Important compatibility note!
+ * Some apps out in the wild create a Notification.Builder in their Activity subclass
+ * constructor for later use. At this point Activities - themselves subclasses of
+ * ContextWrapper - do not have their inner Context populated yet. This means that
+ * any calls to Context methods from within this constructor can cause NPEs in existing
+ * apps. Any data populated from mContext should therefore be populated lazily to
+ * preserve compatibility.
+ */
mContext = context;
// Set defaults to match the defaults of a Notification
@@ -1365,14 +1710,13 @@ public class Notification implements Parcelable
mPriority = PRIORITY_DEFAULT;
mPeople = new ArrayList<String>();
- mPreQuantum = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.L;
mColorUtil = NotificationColorUtil.getInstance();
}
/**
* Add a timestamp pertaining to the notification (usually the time the event occurred).
* It will be shown in the notification content view by default; use
- * {@link Builder#setShowWhen(boolean) setShowWhen} to control this.
+ * {@link #setShowWhen(boolean) setShowWhen} to control this.
*
* @see Notification#when
*/
@@ -1382,7 +1726,7 @@ public class Notification implements Parcelable
}
/**
- * Control whether the timestamp set with {@link Builder#setWhen(long) setWhen} is shown
+ * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
* in the content view.
*/
public Builder setShowWhen(boolean show) {
@@ -1755,17 +2099,64 @@ public class Notification implements Parcelable
}
/**
+ * Set this notification to be part of a group of notifications sharing the same key.
+ * Grouped notifications may display in a cluster or stack on devices which
+ * support such rendering.
+ *
+ * <p>To make this notification the summary for its group, also call
+ * {@link #setGroupSummary}. A sort order can be specified for group members by using
+ * {@link #setSortKey}.
+ * @param groupKey The group key of the group.
+ * @return this object for method chaining
+ */
+ public Builder setGroup(String groupKey) {
+ mGroupKey = groupKey;
+ return this;
+ }
+
+ /**
+ * Set this notification to be the group summary for a group of notifications.
+ * Grouped notifications may display in a cluster or stack on devices which
+ * support such rendering. Requires a group key also be set using {@link #setGroup}.
+ * @param isGroupSummary Whether this notification should be a group summary.
+ * @return this object for method chaining
+ */
+ public Builder setGroupSummary(boolean isGroupSummary) {
+ setFlag(FLAG_GROUP_SUMMARY, isGroupSummary);
+ return this;
+ }
+
+ /**
+ * Set a sort key that orders this notification among other notifications from the
+ * same package. This can be useful if an external sort was already applied and an app
+ * would like to preserve this. Notifications will be sorted lexicographically using this
+ * value, although providing different priorities in addition to providing sort key may
+ * cause this value to be ignored.
+ *
+ * <p>This sort key can also be used to order members of a notification group. See
+ * {@link #setGroup}.
+ *
+ * @see String#compareTo(String)
+ */
+ public Builder setSortKey(String sortKey) {
+ mSortKey = sortKey;
+ return this;
+ }
+
+ /**
* Merge additional metadata into this notification.
*
* <p>Values within the Bundle will replace existing extras values in this Builder.
*
* @see Notification#extras
*/
- public Builder addExtras(Bundle bag) {
- if (mExtras == null) {
- mExtras = new Bundle(bag);
- } else {
- mExtras.putAll(bag);
+ public Builder addExtras(Bundle extras) {
+ if (extras != null) {
+ if (mExtras == null) {
+ mExtras = new Bundle(extras);
+ } else {
+ mExtras.putAll(extras);
+ }
}
return this;
}
@@ -1782,8 +2173,8 @@ public class Notification implements Parcelable
*
* @see Notification#extras
*/
- public Builder setExtras(Bundle bag) {
- mExtras = bag;
+ public Builder setExtras(Bundle extras) {
+ mExtras = extras;
return this;
}
@@ -1827,6 +2218,26 @@ public class Notification implements Parcelable
}
/**
+ * Add an action to this notification. Actions are typically displayed by
+ * the system as a button adjacent to the notification content.
+ * <p>
+ * Every action must have an icon (32dp square and matching the
+ * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
+ * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
+ * <p>
+ * A notification in its expanded form can display up to 3 actions, from left to right in
+ * the order they were added. Actions will not be displayed when the notification is
+ * collapsed, however, so be sure that any essential functions may be accessed by the user
+ * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
+ *
+ * @param action The action to add.
+ */
+ public Builder addAction(Action action) {
+ mActions.add(action);
+ return this;
+ }
+
+ /**
* Add a rich notification style to be applied at build time.
*
* @param style Object responsible for modifying the notification style.
@@ -1843,7 +2254,7 @@ public class Notification implements Parcelable
/**
* Specify the value of {@link #visibility}.
-
+ *
* @param visibility One of {@link #VISIBILITY_PRIVATE} (the default),
* {@link #VISIBILITY_SECRET}, or {@link #VISIBILITY_PUBLIC}.
*
@@ -1865,6 +2276,15 @@ public class Notification implements Parcelable
return this;
}
+ /**
+ * Apply an extender to this notification builder. Extenders may be used to add
+ * metadata or change options on this builder.
+ */
+ public Builder extend(Extender extender) {
+ extender.extend(this);
+ return this;
+ }
+
private void setFlag(int mask, boolean value) {
if (value) {
mFlags |= mask;
@@ -1889,26 +2309,20 @@ public class Notification implements Parcelable
RemoteViews contentView = new RemoteViews(mContext.getPackageName(), resId);
boolean showLine3 = false;
boolean showLine2 = false;
- int smallIconImageViewId = R.id.icon;
+
if (mPriority < PRIORITY_LOW) {
// TODO: Low priority presentation
}
if (mLargeIcon != null) {
contentView.setImageViewBitmap(R.id.icon, mLargeIcon);
processLargeIcon(mLargeIcon, contentView);
- smallIconImageViewId = R.id.right_icon;
- }
- if (mSmallIcon != 0) {
- contentView.setImageViewResource(smallIconImageViewId, mSmallIcon);
- contentView.setViewVisibility(smallIconImageViewId, View.VISIBLE);
- if (mLargeIcon != null) {
- processSmallRightIcon(mSmallIcon, smallIconImageViewId, contentView);
- } else {
- processSmallIconAsLarge(mSmallIcon, contentView);
- }
-
- } else {
- contentView.setViewVisibility(smallIconImageViewId, View.GONE);
+ contentView.setImageViewResource(R.id.right_icon, mSmallIcon);
+ contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
+ processSmallRightIcon(mSmallIcon, contentView);
+ } else { // small icon at left
+ contentView.setImageViewResource(R.id.icon, mSmallIcon);
+ contentView.setViewVisibility(R.id.icon, View.VISIBLE);
+ processSmallIconAsLarge(mSmallIcon, contentView);
}
if (mContentTitle != null) {
contentView.setTextViewText(R.id.title, processLegacyText(mContentTitle));
@@ -1995,14 +2409,12 @@ public class Notification implements Parcelable
int N = mActions.size();
if (N > 0) {
- // Log.d("Notification", "has actions: " + mContentText);
big.setViewVisibility(R.id.actions, View.VISIBLE);
big.setViewVisibility(R.id.action_divider, View.VISIBLE);
if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
big.removeAllViews(R.id.actions);
for (int i=0; i<N; i++) {
final RemoteViews button = generateActionButton(mActions.get(i));
- //Log.d("Notification", "adding action " + i + ": " + mActions.get(i).title);
big.addView(R.id.actions, button);
}
}
@@ -2103,6 +2515,8 @@ public class Notification implements Parcelable
private void processLargeIcon(Bitmap largeIcon, RemoteViews contentView) {
if (!isLegacy() || mColorUtil.isGrayscale(largeIcon)) {
applyLargeIconBackground(contentView);
+ } else {
+ removeLargeIconBackground(contentView);
}
}
@@ -2122,16 +2536,31 @@ public class Notification implements Parcelable
-1);
}
+ private void removeLargeIconBackground(RemoteViews contentView) {
+ contentView.setInt(R.id.icon, "setBackgroundResource", 0);
+ }
+
/**
* Recolor small icons when used in the R.id.right_icon slot.
*/
- private void processSmallRightIcon(int smallIconDrawableId, int smallIconImageViewId,
+ private void processSmallRightIcon(int smallIconDrawableId,
RemoteViews contentView) {
if (!isLegacy() || mColorUtil.isGrayscale(mContext, smallIconDrawableId)) {
- contentView.setDrawableParameters(smallIconImageViewId, false, -1,
- mContext.getResources().getColor(
- R.color.notification_action_legacy_color_filter),
- PorterDuff.Mode.MULTIPLY, -1);
+ contentView.setDrawableParameters(R.id.right_icon, false, -1,
+ 0xFFFFFFFF,
+ PorterDuff.Mode.SRC_ATOP, -1);
+
+ contentView.setInt(R.id.right_icon,
+ "setBackgroundResource",
+ R.drawable.notification_icon_legacy_bg);
+
+ contentView.setDrawableParameters(
+ R.id.right_icon,
+ true,
+ -1,
+ mColor,
+ PorterDuff.Mode.SRC_ATOP,
+ -1);
}
}
@@ -2181,6 +2610,8 @@ public class Notification implements Parcelable
n.flags |= FLAG_SHOW_LIGHTS;
}
n.category = mCategory;
+ n.mGroupKey = mGroupKey;
+ n.mSortKey = mSortKey;
n.priority = mPriority;
if (mActions.size() > 0) {
n.actions = new Action[mActions.size()];
@@ -2692,4 +3123,808 @@ public class Notification implements Parcelable
return wip;
}
}
+
+ /**
+ * Notification style for media playback notifications.
+ *
+ * In the expanded form, {@link Notification#bigContentView}, up to 5
+ * {@link Notification.Action}s specified with
+ * {@link Notification.Builder#addAction(int, CharSequence, PendingIntent) addAction} will be
+ * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
+ * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be
+ * treated as album artwork.
+ *
+ * Unlike the other styles provided here, MediaStyle can also modify the standard-size
+ * {@link Notification#contentView}; by providing action indices to
+ * {@link #setShowActionsInCompactView(int...)} you can promote up to 2 actions to be displayed
+ * in the standard view alongside the usual content.
+ *
+ * Finally, if you attach a {@link android.media.session.MediaSessionToken} using
+ * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSessionToken)},
+ * the System UI can identify this as a notification representing an active media session
+ * and respond accordingly (by showing album artwork in the lockscreen, for example).
+ *
+ * To use this style with your Notification, feed it to
+ * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
+ * <pre class="prettyprint">
+ * Notification noti = new Notification.Builder()
+ * .setSmallIcon(R.drawable.ic_stat_player)
+ * .setContentTitle(&quot;Track title&quot;) // these three lines are optional
+ * .setContentText(&quot;Artist - Album&quot;) // if you use
+ * .setLargeIcon(albumArtBitmap)) // setMediaSession(token, true)
+ * .setMediaSession(mySession, true)
+ * .setStyle(<b>new Notification.MediaStyle()</b>)
+ * .build();
+ * </pre>
+ *
+ * @see Notification#bigContentView
+ */
+ public static class MediaStyle extends Style {
+ static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 2;
+ static final int MAX_MEDIA_BUTTONS = 5;
+
+ private int[] mActionsToShowInCompact = null;
+ private MediaSessionToken mToken;
+
+ public MediaStyle() {
+ }
+
+ public MediaStyle(Builder builder) {
+ setBuilder(builder);
+ }
+
+ /**
+ * Request up to 2 actions (by index in the order of addition) to be shown in the compact
+ * notification view.
+ */
+ public MediaStyle setShowActionsInCompactView(int...actions) {
+ mActionsToShowInCompact = actions;
+ return this;
+ }
+
+ /**
+ * Attach a {@link android.media.session.MediaSessionToken} to this Notification to provide
+ * additional playback information and control to the SystemUI.
+ */
+ public MediaStyle setMediaSession(MediaSessionToken token) {
+ mToken = token;
+ return this;
+ }
+
+ @Override
+ public Notification buildStyled(Notification wip) {
+ wip.contentView = makeMediaContentView();
+ wip.bigContentView = makeMediaBigContentView();
+
+ return wip;
+ }
+
+ /** @hide */
+ @Override
+ public void addExtras(Bundle extras) {
+ super.addExtras(extras);
+
+ if (mToken != null) {
+ extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
+ }
+ }
+
+ private RemoteViews generateMediaActionButton(Action action) {
+ final boolean tombstone = (action.actionIntent == null);
+ RemoteViews button = new RemoteViews(mBuilder.mContext.getPackageName(),
+ R.layout.notification_quantum_media_action);
+ button.setImageViewResource(R.id.action0, action.icon);
+ if (!tombstone) {
+ button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
+ }
+ button.setContentDescription(R.id.action0, action.title);
+ return button;
+ }
+
+ private RemoteViews makeMediaContentView() {
+ RemoteViews view = mBuilder.applyStandardTemplate(
+ R.layout.notification_template_quantum_media, true /* 1U */);
+
+ final int numActions = mBuilder.mActions.size();
+ final int N = mActionsToShowInCompact == null
+ ? 0
+ : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
+ if (N > 0) {
+ view.removeAllViews(R.id.actions);
+ for (int i = 0; i < N; i++) {
+ if (i >= numActions) {
+ throw new IllegalArgumentException(String.format(
+ "setShowActionsInCompactView: action %d out of bounds (max %d)",
+ i, numActions - 1));
+ }
+
+ final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
+ final RemoteViews button = generateMediaActionButton(action);
+ view.addView(R.id.actions, button);
+ }
+ }
+ return view;
+ }
+
+ private RemoteViews makeMediaBigContentView() {
+ RemoteViews big = mBuilder.applyStandardTemplate(
+ R.layout.notification_template_quantum_big_media, false);
+
+ final int N = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
+ if (N > 0) {
+ big.removeAllViews(R.id.actions);
+ for (int i=0; i<N; i++) {
+ final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i));
+ big.addView(R.id.actions, button);
+ }
+ }
+ return big;
+ }
+ }
+
+ /**
+ * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
+ * metadata or change options on a notification builder.
+ */
+ public interface Extender {
+ /**
+ * Apply this extender to a notification builder.
+ * @param builder the builder to be modified.
+ * @return the build object for chaining.
+ */
+ public Builder extend(Builder builder);
+ }
+
+ /**
+ * Helper class to add wearable extensions to notifications.
+ * <p class="note"> See
+ * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
+ * for Android Wear</a> for more information on how to use this class.
+ * <p>
+ * To create a notification with wearable extensions:
+ * <ol>
+ * <li>Create a {@link android.app.Notification.Builder}, setting any desired
+ * properties.
+ * <li>Create a {@link android.app.Notification.WearableExtender}.
+ * <li>Set wearable-specific properties using the
+ * {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}.
+ * <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a
+ * notification.
+ * <li>Post the notification to the notification system with the
+ * {@code NotificationManager.notify(...)} methods.
+ * </ol>
+ *
+ * <pre class="prettyprint">
+ * Notification notif = new Notification.Builder(mContext)
+ * .setContentTitle(&quot;New mail from &quot; + sender.toString())
+ * .setContentText(subject)
+ * .setSmallIcon(R.drawable.new_mail)
+ * .extend(new Notification.WearableExtender()
+ * .setContentIcon(R.drawable.new_mail))
+ * .build();
+ * NotificationManager notificationManger =
+ * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ * notificationManger.notify(0, notif);</pre>
+ *
+ * <p>Wearable extensions can be accessed on an existing notification by using the
+ * {@code WearableExtender(Notification)} constructor,
+ * and then using the {@code get} methods to access values.
+ *
+ * <pre class="prettyprint">
+ * Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
+ * notification);
+ * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
+ */
+ public static final class WearableExtender implements Extender {
+ /**
+ * Sentinel value for an action index that is unset.
+ */
+ public static final int UNSET_ACTION_INDEX = -1;
+
+ /**
+ * Size value for use with {@link #setCustomSizePreset} to show this notification with
+ * default sizing.
+ * <p>For custom display notifications created using {@link #setDisplayIntent},
+ * the default is {@link #SIZE_LARGE}. All other notifications size automatically based
+ * on their content.
+ */
+ public static final int SIZE_DEFAULT = 0;
+
+ /**
+ * Size value for use with {@link #setCustomSizePreset} to show this notification
+ * with an extra small size.
+ * <p>This value is only applicable for custom display notifications created using
+ * {@link #setDisplayIntent}.
+ */
+ public static final int SIZE_XSMALL = 1;
+
+ /**
+ * Size value for use with {@link #setCustomSizePreset} to show this notification
+ * with a small size.
+ * <p>This value is only applicable for custom display notifications created using
+ * {@link #setDisplayIntent}.
+ */
+ public static final int SIZE_SMALL = 2;
+
+ /**
+ * Size value for use with {@link #setCustomSizePreset} to show this notification
+ * with a medium size.
+ * <p>This value is only applicable for custom display notifications created using
+ * {@link #setDisplayIntent}.
+ */
+ public static final int SIZE_MEDIUM = 3;
+
+ /**
+ * Size value for use with {@link #setCustomSizePreset} to show this notification
+ * with a large size.
+ * <p>This value is only applicable for custom display notifications created using
+ * {@link #setDisplayIntent}.
+ */
+ public static final int SIZE_LARGE = 4;
+
+ /**
+ * Size value for use with {@link #setCustomSizePreset} to show this notification
+ * full screen.
+ * <p>This value is only applicable for custom display notifications created using
+ * {@link #setDisplayIntent}.
+ */
+ public static final int SIZE_FULL_SCREEN = 5;
+
+ /** Notification extra which contains wearable extensions */
+ private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
+
+ // Keys within EXTRA_WEARABLE_OPTIONS for wearable options.
+ private static final String KEY_ACTIONS = "actions";
+ private static final String KEY_FLAGS = "flags";
+ private static final String KEY_DISPLAY_INTENT = "displayIntent";
+ private static final String KEY_PAGES = "pages";
+ private static final String KEY_BACKGROUND = "background";
+ private static final String KEY_CONTENT_ICON = "contentIcon";
+ private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
+ private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
+ private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
+ private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
+ private static final String KEY_GRAVITY = "gravity";
+
+ // Flags bitwise-ored to mFlags
+ private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
+ private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
+ private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
+ private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
+
+ // Default value for flags integer
+ private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
+
+ private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END;
+ private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
+
+ private ArrayList<Action> mActions = new ArrayList<Action>();
+ private int mFlags = DEFAULT_FLAGS;
+ private PendingIntent mDisplayIntent;
+ private ArrayList<Notification> mPages = new ArrayList<Notification>();
+ private Bitmap mBackground;
+ private int mContentIcon;
+ private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
+ private int mContentActionIndex = UNSET_ACTION_INDEX;
+ private int mCustomSizePreset = SIZE_DEFAULT;
+ private int mCustomContentHeight;
+ private int mGravity = DEFAULT_GRAVITY;
+
+ /**
+ * Create a {@link android.app.Notification.WearableExtender} with default
+ * options.
+ */
+ public WearableExtender() {
+ }
+
+ public WearableExtender(Notification notif) {
+ Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
+ if (wearableBundle != null) {
+ List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS);
+ if (actions != null) {
+ mActions.addAll(actions);
+ }
+
+ mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
+ mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT);
+
+ Notification[] pages = getNotificationArrayFromBundle(
+ wearableBundle, KEY_PAGES);
+ if (pages != null) {
+ Collections.addAll(mPages, pages);
+ }
+
+ mBackground = wearableBundle.getParcelable(KEY_BACKGROUND);
+ mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
+ mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
+ DEFAULT_CONTENT_ICON_GRAVITY);
+ mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
+ UNSET_ACTION_INDEX);
+ mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
+ SIZE_DEFAULT);
+ mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
+ mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
+ }
+ }
+
+ /**
+ * Apply wearable extensions to a notification that is being built. This is typically
+ * called by the {@link android.app.Notification.Builder#extend} method of
+ * {@link android.app.Notification.Builder}.
+ */
+ @Override
+ public Notification.Builder extend(Notification.Builder builder) {
+ Bundle wearableBundle = new Bundle();
+
+ if (!mActions.isEmpty()) {
+ wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions);
+ }
+ if (mFlags != DEFAULT_FLAGS) {
+ wearableBundle.putInt(KEY_FLAGS, mFlags);
+ }
+ if (mDisplayIntent != null) {
+ wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
+ }
+ if (!mPages.isEmpty()) {
+ wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
+ new Notification[mPages.size()]));
+ }
+ if (mBackground != null) {
+ wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
+ }
+ if (mContentIcon != 0) {
+ wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
+ }
+ if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
+ wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
+ }
+ if (mContentActionIndex != UNSET_ACTION_INDEX) {
+ wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
+ mContentActionIndex);
+ }
+ if (mCustomSizePreset != SIZE_DEFAULT) {
+ wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
+ }
+ if (mCustomContentHeight != 0) {
+ wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
+ }
+ if (mGravity != DEFAULT_GRAVITY) {
+ wearableBundle.putInt(KEY_GRAVITY, mGravity);
+ }
+
+ builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
+ return builder;
+ }
+
+ @Override
+ public WearableExtender clone() {
+ WearableExtender that = new WearableExtender();
+ that.mActions = new ArrayList<Action>(this.mActions);
+ that.mFlags = this.mFlags;
+ that.mDisplayIntent = this.mDisplayIntent;
+ that.mPages = new ArrayList<Notification>(this.mPages);
+ that.mBackground = this.mBackground;
+ that.mContentIcon = this.mContentIcon;
+ that.mContentIconGravity = this.mContentIconGravity;
+ that.mContentActionIndex = this.mContentActionIndex;
+ that.mCustomSizePreset = this.mCustomSizePreset;
+ that.mCustomContentHeight = this.mCustomContentHeight;
+ that.mGravity = this.mGravity;
+ return that;
+ }
+
+ /**
+ * Add a wearable action to this notification.
+ *
+ * <p>When wearable actions are added using this method, the set of actions that
+ * show on a wearable device splits from devices that only show actions added
+ * using {@link android.app.Notification.Builder#addAction}. This allows for customization
+ * of which actions display on different devices.
+ *
+ * @param action the action to add to this notification
+ * @return this object for method chaining
+ * @see android.app.Notification.Action
+ */
+ public WearableExtender addAction(Action action) {
+ mActions.add(action);
+ return this;
+ }
+
+ /**
+ * Adds wearable actions to this notification.
+ *
+ * <p>When wearable actions are added using this method, the set of actions that
+ * show on a wearable device splits from devices that only show actions added
+ * using {@link android.app.Notification.Builder#addAction}. This allows for customization
+ * of which actions display on different devices.
+ *
+ * @param actions the actions to add to this notification
+ * @return this object for method chaining
+ * @see android.app.Notification.Action
+ */
+ public WearableExtender addActions(List<Action> actions) {
+ mActions.addAll(actions);
+ return this;
+ }
+
+ /**
+ * Clear all wearable actions present on this builder.
+ * @return this object for method chaining.
+ * @see #addAction
+ */
+ public WearableExtender clearActions() {
+ mActions.clear();
+ return this;
+ }
+
+ /**
+ * Get the wearable actions present on this notification.
+ */
+ public List<Action> getActions() {
+ return mActions;
+ }
+
+ /**
+ * Set an intent to launch inside of an activity view when displaying
+ * this notification. The {@link PendingIntent} provided should be for an activity.
+ *
+ * <pre class="prettyprint">
+ * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
+ * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
+ * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ * Notification notif = new Notification.Builder(context)
+ * .extend(new Notification.WearableExtender()
+ * .setDisplayIntent(displayPendingIntent)
+ * .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
+ * .build();</pre>
+ *
+ * <p>The activity to launch needs to allow embedding, must be exported, and
+ * should have an empty task affinity.
+ *
+ * <p>Example AndroidManifest.xml entry:
+ * <pre class="prettyprint">
+ * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
+ * android:exported=&quot;true&quot;
+ * android:allowEmbedded=&quot;true&quot;
+ * android:taskAffinity=&quot;&quot; /&gt;</pre>
+ *
+ * @param intent the {@link PendingIntent} for an activity
+ * @return this object for method chaining
+ * @see android.app.Notification.WearableExtender#getDisplayIntent
+ */
+ public WearableExtender setDisplayIntent(PendingIntent intent) {
+ mDisplayIntent = intent;
+ return this;
+ }
+
+ /**
+ * Get the intent to launch inside of an activity view when displaying this
+ * notification. This {@code PendingIntent} should be for an activity.
+ */
+ public PendingIntent getDisplayIntent() {
+ return mDisplayIntent;
+ }
+
+ /**
+ * Add an additional page of content to display with this notification. The current
+ * notification forms the first page, and pages added using this function form
+ * subsequent pages. This field can be used to separate a notification into multiple
+ * sections.
+ *
+ * @param page the notification to add as another page
+ * @return this object for method chaining
+ * @see android.app.Notification.WearableExtender#getPages
+ */
+ public WearableExtender addPage(Notification page) {
+ mPages.add(page);
+ return this;
+ }
+
+ /**
+ * Add additional pages of content to display with this notification. The current
+ * notification forms the first page, and pages added using this function form
+ * subsequent pages. This field can be used to separate a notification into multiple
+ * sections.
+ *
+ * @param pages a list of notifications
+ * @return this object for method chaining
+ * @see android.app.Notification.WearableExtender#getPages
+ */
+ public WearableExtender addPages(List<Notification> pages) {
+ mPages.addAll(pages);
+ return this;
+ }
+
+ /**
+ * Clear all additional pages present on this builder.
+ * @return this object for method chaining.
+ * @see #addPage
+ */
+ public WearableExtender clearPages() {
+ mPages.clear();
+ return this;
+ }
+
+ /**
+ * Get the array of additional pages of content for displaying this notification. The
+ * current notification forms the first page, and elements within this array form
+ * subsequent pages. This field can be used to separate a notification into multiple
+ * sections.
+ * @return the pages for this notification
+ */
+ public List<Notification> getPages() {
+ return mPages;
+ }
+
+ /**
+ * Set a background image to be displayed behind the notification content.
+ * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
+ * will work with any notification style.
+ *
+ * @param background the background bitmap
+ * @return this object for method chaining
+ * @see android.app.Notification.WearableExtender#getBackground
+ */
+ public WearableExtender setBackground(Bitmap background) {
+ mBackground = background;
+ return this;
+ }
+
+ /**
+ * Get a background image to be displayed behind the notification content.
+ * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background
+ * will work with any notification style.
+ *
+ * @return the background image
+ * @see android.app.Notification.WearableExtender#setBackground
+ */
+ public Bitmap getBackground() {
+ return mBackground;
+ }
+
+ /**
+ * Set an icon that goes with the content of this notification.
+ */
+ public WearableExtender setContentIcon(int icon) {
+ mContentIcon = icon;
+ return this;
+ }
+
+ /**
+ * Get an icon that goes with the content of this notification.
+ */
+ public int getContentIcon() {
+ return mContentIcon;
+ }
+
+ /**
+ * Set the gravity that the content icon should have within the notification display.
+ * Supported values include {@link android.view.Gravity#START} and
+ * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
+ * @see #setContentIcon
+ */
+ public WearableExtender setContentIconGravity(int contentIconGravity) {
+ mContentIconGravity = contentIconGravity;
+ return this;
+ }
+
+ /**
+ * Get the gravity that the content icon should have within the notification display.
+ * Supported values include {@link android.view.Gravity#START} and
+ * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
+ * @see #getContentIcon
+ */
+ public int getContentIconGravity() {
+ return mContentIconGravity;
+ }
+
+ /**
+ * Set an action from this notification's actions to be clickable with the content of
+ * this notification. This action will no longer display separately from the
+ * notification's content.
+ *
+ * <p>For notifications with multiple pages, child pages can also have content actions
+ * set, although the list of available actions comes from the main notification and not
+ * from the child page's notification.
+ *
+ * @param actionIndex The index of the action to hoist onto the current notification page.
+ * If wearable actions were added to the main notification, this index
+ * will apply to that list, otherwise it will apply to the regular
+ * actions list.
+ */
+ public WearableExtender setContentAction(int actionIndex) {
+ mContentActionIndex = actionIndex;
+ return this;
+ }
+
+ /**
+ * Get the index of the notification action, if any, that was specified as being clickable
+ * with the content of this notification. This action will no longer display separately
+ * from the notification's content.
+ *
+ * <p>For notifications with multiple pages, child pages can also have content actions
+ * set, although the list of available actions comes from the main notification and not
+ * from the child page's notification.
+ *
+ * <p>If wearable specific actions were added to the main notification, this index will
+ * apply to that list, otherwise it will apply to the regular actions list.
+ *
+ * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
+ */
+ public int getContentAction() {
+ return mContentActionIndex;
+ }
+
+ /**
+ * Set the gravity that this notification should have within the available viewport space.
+ * Supported values include {@link android.view.Gravity#TOP},
+ * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
+ * The default value is {@link android.view.Gravity#BOTTOM}.
+ */
+ public WearableExtender setGravity(int gravity) {
+ mGravity = gravity;
+ return this;
+ }
+
+ /**
+ * Get the gravity that this notification should have within the available viewport space.
+ * Supported values include {@link android.view.Gravity#TOP},
+ * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
+ * The default value is {@link android.view.Gravity#BOTTOM}.
+ */
+ public int getGravity() {
+ return mGravity;
+ }
+
+ /**
+ * Set the custom size preset for the display of this notification out of the available
+ * presets found in {@link android.app.Notification.WearableExtender}, e.g.
+ * {@link #SIZE_LARGE}.
+ * <p>Some custom size presets are only applicable for custom display notifications created
+ * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the
+ * documentation for the preset in question. See also
+ * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
+ */
+ public WearableExtender setCustomSizePreset(int sizePreset) {
+ mCustomSizePreset = sizePreset;
+ return this;
+ }
+
+ /**
+ * Get the custom size preset for the display of this notification out of the available
+ * presets found in {@link android.app.Notification.WearableExtender}, e.g.
+ * {@link #SIZE_LARGE}.
+ * <p>Some custom size presets are only applicable for custom display notifications created
+ * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
+ * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
+ */
+ public int getCustomSizePreset() {
+ return mCustomSizePreset;
+ }
+
+ /**
+ * Set the custom height in pixels for the display of this notification's content.
+ * <p>This option is only available for custom display notifications created
+ * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also
+ * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
+ * {@link #getCustomContentHeight}.
+ */
+ public WearableExtender setCustomContentHeight(int height) {
+ mCustomContentHeight = height;
+ return this;
+ }
+
+ /**
+ * Get the custom height in pixels for the display of this notification's content.
+ * <p>This option is only available for custom display notifications created
+ * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
+ * {@link #setCustomContentHeight}.
+ */
+ public int getCustomContentHeight() {
+ return mCustomContentHeight;
+ }
+
+ /**
+ * Set whether the scrolling position for the contents of this notification should start
+ * at the bottom of the contents instead of the top when the contents are too long to
+ * display within the screen. Default is false (start scroll at the top).
+ */
+ public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
+ setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
+ return this;
+ }
+
+ /**
+ * Get whether the scrolling position for the contents of this notification should start
+ * at the bottom of the contents instead of the top when the contents are too long to
+ * display within the screen. Default is false (start scroll at the top).
+ */
+ public boolean getStartScrollBottom() {
+ return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
+ }
+
+ /**
+ * Set whether the content intent is available when the wearable device is not connected
+ * to a companion device. The user can still trigger this intent when the wearable device
+ * is offline, but a visual hint will indicate that the content intent may not be available.
+ * Defaults to true.
+ */
+ public WearableExtender setContentIntentAvailableOffline(
+ boolean contentIntentAvailableOffline) {
+ setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
+ return this;
+ }
+
+ /**
+ * Get whether the content intent is available when the wearable device is not connected
+ * to a companion device. The user can still trigger this intent when the wearable device
+ * is offline, but a visual hint will indicate that the content intent may not be available.
+ * Defaults to true.
+ */
+ public boolean getContentIntentAvailableOffline() {
+ return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
+ }
+
+ /**
+ * Set a hint that this notification's icon should not be displayed.
+ * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
+ * @return this object for method chaining
+ */
+ public WearableExtender setHintHideIcon(boolean hintHideIcon) {
+ setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
+ return this;
+ }
+
+ /**
+ * Get a hint that this notification's icon should not be displayed.
+ * @return {@code true} if this icon should not be displayed, false otherwise.
+ * The default value is {@code false} if this was never set.
+ */
+ public boolean getHintHideIcon() {
+ return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
+ }
+
+ /**
+ * Set a visual hint that only the background image of this notification should be
+ * displayed, and other semantic content should be hidden. This hint is only applicable
+ * to sub-pages added using {@link #addPage}.
+ */
+ public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
+ setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
+ return this;
+ }
+
+ /**
+ * Get a visual hint that only the background image of this notification should be
+ * displayed, and other semantic content should be hidden. This hint is only applicable
+ * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
+ */
+ public boolean getHintShowBackgroundOnly() {
+ return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
+ }
+
+ private void setFlag(int mask, boolean value) {
+ if (value) {
+ mFlags |= mask;
+ } else {
+ mFlags &= ~mask;
+ }
+ }
+ }
+
+ /**
+ * Get an array of Notification objects from a parcelable array bundle field.
+ * Update the bundle to have a typed array so fetches in the future don't need
+ * to do an array copy.
+ */
+ private static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) {
+ Parcelable[] array = bundle.getParcelableArray(key);
+ if (array instanceof Notification[] || array == null) {
+ return (Notification[]) array;
+ }
+ Notification[] typedArray = Arrays.copyOf(array, array.length,
+ Notification[].class);
+ bundle.putParcelableArray(key, typedArray);
+ return typedArray;
+ }
}
diff --git a/core/java/android/app/PackageInstallObserver.java b/core/java/android/app/PackageInstallObserver.java
index dacffb4..941efbd 100644
--- a/core/java/android/app/PackageInstallObserver.java
+++ b/core/java/android/app/PackageInstallObserver.java
@@ -18,32 +18,36 @@ 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.
- */
+/** {@hide} */
public class PackageInstallObserver {
- IPackageInstallObserver2.Stub mObserver = new IPackageInstallObserver2.Stub() {
+ private final IPackageInstallObserver2.Stub mBinder = new IPackageInstallObserver2.Stub() {
@Override
- public void packageInstalled(String pkgName, Bundle extras, int result)
- throws RemoteException {
- PackageInstallObserver.this.packageInstalled(pkgName, extras, result);
+ public void packageInstalled(String basePackageName, Bundle extras, int returnCode) {
+ PackageInstallObserver.this.packageInstalled(basePackageName, extras, returnCode);
}
};
+ /** {@hide} */
+ public IPackageInstallObserver2.Stub getBinder() {
+ return mBinder;
+ }
+
/**
- * This method will be called to report the result of the package installation attempt.
+ * 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
+ * @param basePackageName 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 returnCode The numeric success or failure code indicating the
+ * basic outcome
+ * @hide
*/
- public void packageInstalled(String pkgName, Bundle extras, int result) {
+ public void packageInstalled(String basePackageName, Bundle extras, int returnCode) {
}
}
diff --git a/core/java/android/tv/ITvInputService.aidl b/core/java/android/app/PackageUninstallObserver.java
index 4f1bc2b..0a960a7 100644
--- a/core/java/android/tv/ITvInputService.aidl
+++ b/core/java/android/app/PackageUninstallObserver.java
@@ -14,18 +14,24 @@
* limitations under the License.
*/
-package android.tv;
+package android.app;
-import android.tv.ITvInputServiceCallback;
-import android.tv.ITvInputSessionCallback;
-import android.view.InputChannel;
+import android.content.pm.IPackageDeleteObserver;
-/**
- * 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);
+/** {@hide} */
+public class PackageUninstallObserver {
+ private final IPackageDeleteObserver.Stub mBinder = new IPackageDeleteObserver.Stub() {
+ @Override
+ public void packageDeleted(String basePackageName, int returnCode) {
+ PackageUninstallObserver.this.onUninstallFinished(basePackageName, returnCode);
+ }
+ };
+
+ /** {@hide} */
+ public IPackageDeleteObserver.Stub getBinder() {
+ return mBinder;
+ }
+
+ public void onUninstallFinished(String basePackageName, int returnCode) {
+ }
}
diff --git a/core/java/android/app/RemoteInput.java b/core/java/android/app/RemoteInput.java
new file mode 100644
index 0000000..11420c5
--- /dev/null
+++ b/core/java/android/app/RemoteInput.java
@@ -0,0 +1,311 @@
+/*
+ * 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.ClipData;
+import android.content.ClipDescription;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A {@code RemoteInput} object specifies input to be collected from a user to be passed along with
+ * an intent inside a {@link android.app.PendingIntent} that is sent.
+ * Always use {@link RemoteInput.Builder} to create instances of this class.
+ * <p class="note"> See
+ * <a href="{@docRoot}wear/notifications/remote-input.html">Receiving Voice Input from
+ * a Notification</a> for more information on how to use this class.
+ *
+ * <p>The following example adds a {@code RemoteInput} to a {@link Notification.Action},
+ * sets the result key as {@code quick_reply}, and sets the label as {@code Quick reply}.
+ * Users are prompted to input a response when they trigger the action. The results are sent along
+ * with the intent and can be retrieved with the result key (provided to the {@link Builder}
+ * constructor) from the Bundle returned by {@link #getResultsFromIntent}.
+ *
+ * <pre class="prettyprint">
+ * public static final String KEY_QUICK_REPLY_TEXT = "quick_reply";
+ * Notification.Action action = new Notification.Action.Builder(
+ * R.drawable.reply, &quot;Reply&quot;, actionIntent)
+ * <b>.addRemoteInput(new RemoteInput.Builder(KEY_QUICK_REPLY_TEXT)
+ * .setLabel("Quick reply").build()</b>)
+ * .build();</pre>
+ *
+ * <p>When the {@link android.app.PendingIntent} is fired, the intent inside will contain the
+ * input results if collected. To access these results, use the {@link #getResultsFromIntent}
+ * function. The result values will present under the result key passed to the {@link Builder}
+ * constructor.
+ *
+ * <pre class="prettyprint">
+ * public static final String KEY_QUICK_REPLY_TEXT = "quick_reply";
+ * Bundle results = RemoteInput.getResultsFromIntent(intent);
+ * if (results != null) {
+ * CharSequence quickReplyResult = results.getCharSequence(KEY_QUICK_REPLY_TEXT);
+ * }</pre>
+ */
+public final class RemoteInput implements Parcelable {
+ /** Label used to denote the clip data type used for remote input transport */
+ public static final String RESULTS_CLIP_LABEL = "android.remoteinput.results";
+
+ /** Extra added to a clip data intent object to hold the results bundle. */
+ public static final String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData";
+
+ // Flags bitwise-ored to mFlags
+ private static final int FLAG_ALLOW_FREE_FORM_INPUT = 0x1;
+
+ // Default value for flags integer
+ private static final int DEFAULT_FLAGS = FLAG_ALLOW_FREE_FORM_INPUT;
+
+ private final String mResultKey;
+ private final CharSequence mLabel;
+ private final CharSequence[] mChoices;
+ private final int mFlags;
+ private final Bundle mExtras;
+
+ private RemoteInput(String resultKey, CharSequence label, CharSequence[] choices,
+ int flags, Bundle extras) {
+ this.mResultKey = resultKey;
+ this.mLabel = label;
+ this.mChoices = choices;
+ this.mFlags = flags;
+ this.mExtras = extras;
+ }
+
+ /**
+ * Get the key that the result of this input will be set in from the Bundle returned by
+ * {@link #getResultsFromIntent} when the {@link android.app.PendingIntent} is sent.
+ */
+ public String getResultKey() {
+ return mResultKey;
+ }
+
+ /**
+ * Get the label to display to users when collecting this input.
+ */
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Get possible input choices. This can be {@code null} if there are no choices to present.
+ */
+ public CharSequence[] getChoices() {
+ return mChoices;
+ }
+
+ /**
+ * Get whether or not users can provide an arbitrary value for
+ * input. If you set this to {@code false}, users must select one of the
+ * choices in {@link #getChoices}. An {@link IllegalArgumentException} is thrown
+ * if you set this to false and {@link #getChoices} returns {@code null} or empty.
+ */
+ public boolean getAllowFreeFormInput() {
+ return (mFlags & FLAG_ALLOW_FREE_FORM_INPUT) != 0;
+ }
+
+ /**
+ * Get additional metadata carried around with this remote input.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Builder class for {@link RemoteInput} objects.
+ */
+ public static final class Builder {
+ private final String mResultKey;
+ private CharSequence mLabel;
+ private CharSequence[] mChoices;
+ private int mFlags = DEFAULT_FLAGS;
+ private Bundle mExtras = new Bundle();
+
+ /**
+ * Create a builder object for {@link RemoteInput} objects.
+ * @param resultKey the Bundle key that refers to this input when collected from the user
+ */
+ public Builder(String resultKey) {
+ if (resultKey == null) {
+ throw new IllegalArgumentException("Result key can't be null");
+ }
+ mResultKey = resultKey;
+ }
+
+ /**
+ * Set a label to be displayed to the user when collecting this input.
+ * @param label The label to show to users when they input a response.
+ * @return this object for method chaining
+ */
+ public Builder setLabel(CharSequence label) {
+ mLabel = Notification.safeCharSequence(label);
+ return this;
+ }
+
+ /**
+ * Specifies choices available to the user to satisfy this input.
+ * @param choices an array of pre-defined choices for users input.
+ * You must provide a non-null and non-empty array if
+ * you disabled free form input using {@link #setAllowFreeFormInput}.
+ * @return this object for method chaining
+ */
+ public Builder setChoices(CharSequence[] choices) {
+ if (choices == null) {
+ mChoices = null;
+ } else {
+ mChoices = new CharSequence[choices.length];
+ for (int i = 0; i < choices.length; i++) {
+ mChoices[i] = Notification.safeCharSequence(choices[i]);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Specifies whether the user can provide arbitrary values.
+ *
+ * @param allowFreeFormInput The default is {@code true}.
+ * If you specify {@code false}, you must provide a non-null
+ * and non-empty array to {@link #setChoices} or an
+ * {@link IllegalArgumentException} is thrown.
+ * @return this object for method chaining
+ */
+ public Builder setAllowFreeFormInput(boolean allowFreeFormInput) {
+ setFlag(mFlags, allowFreeFormInput);
+ return this;
+ }
+
+ /**
+ * Merge additional metadata into this builder.
+ *
+ * <p>Values within the Bundle will replace existing extras values in this Builder.
+ *
+ * @see RemoteInput#getExtras
+ */
+ public Builder addExtras(Bundle extras) {
+ if (extras != null) {
+ mExtras.putAll(extras);
+ }
+ return this;
+ }
+
+ /**
+ * Get the metadata Bundle used by this Builder.
+ *
+ * <p>The returned Bundle is shared with this Builder.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ private void setFlag(int mask, boolean value) {
+ if (value) {
+ mFlags |= mask;
+ } else {
+ mFlags &= ~mask;
+ }
+ }
+
+ /**
+ * Combine all of the options that have been set and return a new {@link RemoteInput}
+ * object.
+ */
+ public RemoteInput build() {
+ return new RemoteInput(mResultKey, mLabel, mChoices, mFlags, mExtras);
+ }
+ }
+
+ private RemoteInput(Parcel in) {
+ mResultKey = in.readString();
+ mLabel = in.readCharSequence();
+ mChoices = in.readCharSequenceArray();
+ mFlags = in.readInt();
+ mExtras = in.readBundle();
+ }
+
+ /**
+ * Get the remote input results bundle from an intent. The returned Bundle will
+ * contain a key/value for every result key populated by remote input collector.
+ * Use the {@link Bundle#getCharSequence(String)} method to retrieve a value.
+ * @param intent The intent object that fired in response to an action or content intent
+ * which also had one or more remote input requested.
+ */
+ public static Bundle getResultsFromIntent(Intent intent) {
+ ClipData clipData = intent.getClipData();
+ if (clipData == null) {
+ return null;
+ }
+ ClipDescription clipDescription = clipData.getDescription();
+ if (!clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) {
+ return null;
+ }
+ if (clipDescription.getLabel().equals(RESULTS_CLIP_LABEL)) {
+ return clipData.getItemAt(0).getIntent().getExtras().getParcelable(EXTRA_RESULTS_DATA);
+ }
+ return null;
+ }
+
+ /**
+ * Populate an intent object with the results gathered from remote input. This method
+ * should only be called by remote input collection services when sending results to a
+ * pending intent.
+ * @param remoteInputs The remote inputs for which results are being provided
+ * @param intent The intent to add remote inputs to. The {@link ClipData}
+ * field of the intent will be modified to contain the results.
+ * @param results A bundle holding the remote input results. This bundle should
+ * be populated with keys matching the result keys specified in
+ * {@code remoteInputs} with values being the result per key.
+ */
+ public static void addResultsToIntent(RemoteInput[] remoteInputs, Intent intent,
+ Bundle results) {
+ Bundle resultsBundle = new Bundle();
+ for (RemoteInput remoteInput : remoteInputs) {
+ Object result = results.get(remoteInput.getResultKey());
+ if (result instanceof CharSequence) {
+ resultsBundle.putCharSequence(remoteInput.getResultKey(), (CharSequence) result);
+ }
+ }
+ Intent clipIntent = new Intent();
+ clipIntent.putExtra(EXTRA_RESULTS_DATA, resultsBundle);
+ intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipIntent));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mResultKey);
+ out.writeCharSequence(mLabel);
+ out.writeCharSequenceArray(mChoices);
+ out.writeInt(mFlags);
+ out.writeBundle(mExtras);
+ }
+
+ public static final Creator<RemoteInput> CREATOR = new Creator<RemoteInput>() {
+ @Override
+ public RemoteInput createFromParcel(Parcel in) {
+ return new RemoteInput(in);
+ }
+
+ @Override
+ public RemoteInput[] newArray(int size) {
+ return new RemoteInput[size];
+ }
+ };
+}
diff --git a/core/java/android/app/SharedElementListener.java b/core/java/android/app/SharedElementListener.java
new file mode 100644
index 0000000..e03e42e
--- /dev/null
+++ b/core/java/android/app/SharedElementListener.java
@@ -0,0 +1,114 @@
+/*
+ * 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.view.View;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Listener provided in
+ * {@link Activity#setEnterSharedElementListener(SharedElementListener)} and
+ * {@link Activity#setExitSharedElementListener(SharedElementListener)}
+ * to monitor the Activity transitions. The events can be used to customize Activity
+ * Transition behavior.
+ */
+public abstract class SharedElementListener {
+
+ static final SharedElementListener NULL_LISTENER = new SharedElementListener() {
+ };
+
+ /**
+ * Called to allow the listener to customize the start state of the shared element when
+ * transferring in shared element state.
+ * <p>
+ * The shared element will start at the size and position of the shared element
+ * in the launching Activity or Fragment. It will also transfer ImageView scaleType
+ * and imageMatrix if the shared elements in the calling and called Activities are
+ * ImageViews. Some applications may want to make additional changes, such as
+ * changing the clip bounds, scaling, or rotation if the shared element end state
+ * does not map well to the start state.
+ * </p>
+ *
+ * @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 setSharedElementStart(List<String> sharedElementNames,
+ List<View> sharedElements, List<View> sharedElementSnapshots) {}
+
+ /**
+ * Called to allow the listener to customize the end state of the shared element when
+ * transferring in shared element state.
+ * <p>
+ * Any customization done in
+ * {@link #setSharedElementStart(java.util.List, java.util.List, java.util.List)}
+ * may need to be modified to the final state of the shared element if it is not
+ * automatically corrected by layout. For example, rotation or scale will not
+ * be affected by layout and if changed in {@link #setSharedElementStart(java.util.List,
+ * java.util.List, java.util.List)}, it will also have to be set here again to correct
+ * the end state.
+ * </p>
+ *
+ * @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 setSharedElementEnd(List<String> sharedElementNames,
+ List<View> sharedElements, List<View> sharedElementSnapshots) {}
+
+ /**
+ * Called after {@link #remapSharedElements(java.util.List, java.util.Map)} when
+ * transferring shared elements in. Any shared elements that have no mapping will be in
+ * <var>rejectedSharedElements</var>. The elements remaining in
+ * <var>rejectedSharedElements</var> will be transitioned out of the Scene. If a
+ * View is removed from <var>rejectedSharedElements</var>, it must be handled by the
+ * <code>SharedElementListener</code>.
+ * <p>
+ * Views in rejectedSharedElements will have their position and size set to the
+ * position of the calling shared element, relative to the Window decor View and contain
+ * snapshots of the View from the calling Activity or Fragment. This
+ * view may be safely added to the decor View's overlay to remain in position.
+ * </p>
+ *
+ * @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. A
+ * View removed from this list will not be transitioned
+ * automatically.
+ */
+ public void handleRejectedSharedElements(List<View> rejectedSharedElements) {}
+
+ /**
+ * Lets the ActivityTransitionListener adjust the mapping of shared element names to
+ * Views.
+ *
+ * @param names The names of all shared elements transferred from the calling Activity
+ * to the started Activity.
+ * @param sharedElements The mapping of shared element names to Views. The best guess
+ * will be filled into sharedElements based on the View names.
+ */
+ public void remapSharedElements(List<String> names, Map<String, View> sharedElements) {}
+}
diff --git a/core/java/android/app/TaskManagerImpl.java b/core/java/android/app/TaskManagerImpl.java
new file mode 100644
index 0000000..f42839e
--- /dev/null
+++ b/core/java/android/app/TaskManagerImpl.java
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+// in android.app so ContextImpl has package access
+package android.app;
+
+import android.app.task.ITaskManager;
+import android.app.task.Task;
+import android.app.task.TaskManager;
+
+import java.util.List;
+
+
+/**
+ * Concrete implementation of the TaskManager interface
+ * @hide
+ */
+public class TaskManagerImpl extends TaskManager {
+ ITaskManager mBinder;
+
+ /* package */ TaskManagerImpl(ITaskManager binder) {
+ mBinder = binder;
+ }
+
+ @Override
+ public int schedule(Task task) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public void cancel(int taskId) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void cancelAll() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public List<Task> getAllPendingTasks() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+}
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index a85c61f..8cf8c25 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -110,6 +110,7 @@ public class TimePickerDialog extends AlertDialog
(LayoutInflater) themeContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.time_picker_dialog, null);
setView(view);
+ setButtonPanelLayoutHint(LAYOUT_HINT_SIDE);
mTimePicker = (TimePicker) view.findViewById(R.id.timePicker);
// Initialize state
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 9405325..64e3484 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -26,6 +26,7 @@ import android.graphics.Canvas;
import android.graphics.Point;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Looper;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
@@ -40,7 +41,9 @@ import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
+import libcore.io.IoUtils;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
@@ -840,6 +843,44 @@ public final class UiAutomation {
return null;
}
+ /**
+ * Executes a shell command. This method returs a file descriptor that points
+ * to the standard output stream. The command execution is similar to running
+ * "adb shell <command>" from a host connected to the device.
+ * <p>
+ * <strong>Note:</strong> It is your responsibility to close the retunred file
+ * descriptor once you are done reading.
+ * </p>
+ *
+ * @param command The command to execute.
+ * @return A file descriptor to the standard output stream.
+ */
+ public ParcelFileDescriptor executeShellCommand(String command) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+
+ ParcelFileDescriptor source = null;
+ ParcelFileDescriptor sink = null;
+
+ try {
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ source = pipe[0];
+ sink = pipe[1];
+
+ // Calling out without a lock held.
+ mUiAutomationConnection.executeShellCommand(command, sink);
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Error executing shell command!", ioe);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error executing shell command!", re);
+ } finally {
+ IoUtils.closeQuietly(sink);
+ }
+
+ return source;
+ }
+
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 fa40286..81bcb39 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -23,6 +23,7 @@ import android.graphics.Bitmap;
import android.hardware.input.InputManager;
import android.os.Binder;
import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -33,6 +34,12 @@ import android.view.WindowAnimationFrameStats;
import android.view.WindowContentFrameStats;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.IAccessibilityManager;
+import libcore.io.IoUtils;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
/**
* This is a remote object that is passed from the shell to an instrumentation
@@ -50,8 +57,8 @@ 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 IAccessibilityManager mAccessibilityManager = IAccessibilityManager.Stub
+ .asInterface(ServiceManager.getService(Service.ACCESSIBILITY_SERVICE));
private final Object mLock = new Object();
@@ -220,6 +227,41 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
}
@Override
+ public void executeShellCommand(String command, ParcelFileDescriptor sink)
+ throws RemoteException {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+
+ InputStream in = null;
+ OutputStream out = null;
+
+ try {
+ java.lang.Process process = Runtime.getRuntime().exec(command);
+
+ in = process.getInputStream();
+ out = new FileOutputStream(sink.getFileDescriptor());
+
+ final byte[] buffer = new byte[8192];
+ while (true) {
+ final int readByteCount = in.read(buffer);
+ if (readByteCount < 0) {
+ break;
+ }
+ out.write(buffer, 0, readByteCount);
+ }
+ } catch (IOException ioe) {
+ throw new RuntimeException("Error running shell command", ioe);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ IoUtils.closeQuietly(sink);
+ }
+ }
+
+ @Override
public void shutdown() {
synchronized (mLock) {
if (isConnectedLocked()) {
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index 6dc48b0..85e970c 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -30,18 +30,39 @@ import com.android.internal.app.IVoiceInteractorRequest;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
-import java.util.WeakHashMap;
+import java.util.ArrayList;
/**
- * Interface for an {@link Activity} to interact with the user through voice.
+ * Interface for an {@link Activity} to interact with the user through voice. Use
+ * {@link android.app.Activity#getVoiceInteractor() Activity.getVoiceInteractor}
+ * to retrieve the interface, if the activity is currently involved in a voice interaction.
+ *
+ * <p>The voice interactor revolves around submitting voice interaction requests to the
+ * back-end voice interaction service that is working with the user. These requests are
+ * submitted with {@link #submitRequest}, providing a new instance of a
+ * {@link Request} subclass describing the type of operation to perform -- currently the
+ * possible requests are {@link ConfirmationRequest} and {@link CommandRequest}.
+ *
+ * <p>Once a request is submitted, the voice system will process it and eventually deliver
+ * the result to the request object. The application can cancel a pending request at any
+ * time.
+ *
+ * <p>The VoiceInteractor is integrated with Activity's state saving mechanism, so that
+ * if an activity is being restarted with retained state, it will retain the current
+ * VoiceInteractor and any outstanding requests. Because of this, you should always use
+ * {@link Request#getActivity() Request.getActivity} to get back to the activity of a
+ * request, rather than holding on to the activity instance yourself, either explicitly
+ * or implicitly through a non-static inner class.
*/
public class VoiceInteractor {
static final String TAG = "VoiceInteractor";
static final boolean DEBUG = true;
- final Context mContext;
- final Activity mActivity;
final IVoiceInteractor mInteractor;
+
+ Context mContext;
+ Activity mActivity;
+
final HandlerCaller mHandlerCaller;
final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() {
@Override
@@ -60,6 +81,16 @@ public class VoiceInteractor {
request.clear();
}
break;
+ case MSG_ABORT_VOICE_RESULT:
+ request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
+ if (DEBUG) Log.d(TAG, "onAbortVoice: req="
+ + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
+ + " result=" + args.arg1);
+ if (request != null) {
+ ((AbortVoiceRequest)request).onAbortResult((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="
@@ -94,6 +125,12 @@ public class VoiceInteractor {
}
@Override
+ public void deliverAbortVoiceResult(IVoiceInteractorRequest request, Bundle result) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
+ MSG_ABORT_VOICE_RESULT, request, result));
+ }
+
+ @Override
public void deliverCommandResult(IVoiceInteractorRequest request, boolean complete,
Bundle result) {
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
@@ -110,8 +147,9 @@ public class VoiceInteractor {
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;
+ static final int MSG_ABORT_VOICE_RESULT = 2;
+ static final int MSG_COMMAND_RESULT = 3;
+ static final int MSG_CANCEL_RESULT = 4;
public static abstract class Request {
IVoiceInteractorRequest mRequestInterface;
@@ -140,6 +178,12 @@ public class VoiceInteractor {
public void onCancel() {
}
+ public void onAttached(Activity activity) {
+ }
+
+ public void onDetached() {
+ }
+
void clear() {
mRequestInterface = null;
mContext = null;
@@ -180,9 +224,42 @@ public class VoiceInteractor {
IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
IVoiceInteractorCallback callback) throws RemoteException {
- return interactor.startConfirmation(packageName, callback, mPrompt.toString(), mExtras);
+ return interactor.startConfirmation(packageName, callback, mPrompt, mExtras);
+ }
+ }
+
+ public static class AbortVoiceRequest extends Request {
+ final CharSequence mMessage;
+ final Bundle mExtras;
+
+ /**
+ * Reports that the current interaction can not be complete with voice, so the
+ * application will need to switch to a traditional input UI. Applications should
+ * only use this when they need to completely bail out of the voice interaction
+ * and switch to a traditional UI. When the response comes back, the voice
+ * system has handled the request and is ready to switch; at that point the application
+ * can start a new non-voice activity. Be sure when starting the new activity
+ * to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK
+ * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice
+ * interaction task.
+ *
+ * @param message Optional message to tell user about not being able to complete
+ * the interaction with voice.
+ * @param extras Additional optional information.
+ */
+ public AbortVoiceRequest(CharSequence message, Bundle extras) {
+ mMessage = message;
+ mExtras = extras;
}
- }
+
+ public void onAbortResult(Bundle result) {
+ }
+
+ IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
+ IVoiceInteractorCallback callback) throws RemoteException {
+ return interactor.startAbortVoice(packageName, callback, mMessage, mExtras);
+ }
+ }
public static class CommandRequest extends Request {
final String mCommand;
@@ -220,11 +297,11 @@ public class VoiceInteractor {
}
}
- VoiceInteractor(Context context, Activity activity, IVoiceInteractor interactor,
+ VoiceInteractor(IVoiceInteractor interactor, Context context, Activity activity,
Looper looper) {
+ mInteractor = interactor;
mContext = context;
mActivity = activity;
- mInteractor = interactor;
mHandlerCaller = new HandlerCaller(context, looper, mHandlerCallerCallback, true);
}
@@ -238,6 +315,49 @@ public class VoiceInteractor {
}
}
+ private ArrayList<Request> makeRequestList() {
+ final int N = mActiveRequests.size();
+ if (N < 1) {
+ return null;
+ }
+ ArrayList<Request> list = new ArrayList<Request>(N);
+ for (int i=0; i<N; i++) {
+ list.add(mActiveRequests.valueAt(i));
+ }
+ return list;
+ }
+
+ void attachActivity(Activity activity) {
+ if (mActivity == activity) {
+ return;
+ }
+ mContext = activity;
+ mActivity = activity;
+ ArrayList<Request> reqs = makeRequestList();
+ if (reqs != null) {
+ for (int i=0; i<reqs.size(); i++) {
+ Request req = reqs.get(i);
+ req.mContext = activity;
+ req.mActivity = activity;
+ req.onAttached(activity);
+ }
+ }
+ }
+
+ void detachActivity() {
+ ArrayList<Request> reqs = makeRequestList();
+ if (reqs != null) {
+ for (int i=0; i<reqs.size(); i++) {
+ Request req = reqs.get(i);
+ req.onDetached();
+ req.mActivity = null;
+ req.mContext = null;
+ }
+ }
+ mContext = null;
+ mActivity = null;
+ }
+
public boolean submitRequest(Request request) {
try {
IVoiceInteractorRequest ireq = request.submit(mInteractor,
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 58d707c..48ff5b6 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -911,6 +911,35 @@ public class WallpaperManager {
*/
public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) {
try {
+ /**
+ * The framework makes no attempt to limit the window size
+ * to the maximum texture size. Any window larger than this
+ * cannot be composited.
+ *
+ * Read maximum texture size from system property and scale down
+ * minimumWidth and minimumHeight accordingly.
+ */
+ int maximumTextureSize;
+ try {
+ maximumTextureSize = SystemProperties.getInt("sys.max_texture_size", 0);
+ } catch (Exception e) {
+ maximumTextureSize = 0;
+ }
+
+ if (maximumTextureSize > 0) {
+ if ((minimumWidth > maximumTextureSize) ||
+ (minimumHeight > maximumTextureSize)) {
+ float aspect = (float)minimumHeight / (float)minimumWidth;
+ if (minimumWidth > minimumHeight) {
+ minimumWidth = maximumTextureSize;
+ minimumHeight = (int)((minimumWidth * aspect) + 0.5);
+ } else {
+ minimumHeight = maximumTextureSize;
+ minimumWidth = (int)((minimumHeight / aspect) + 0.5);
+ }
+ }
+ }
+
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
} else {
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index f9d9059..ee222a9 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -166,12 +166,11 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
/**
* Broadcast Action: This broadcast is sent to the newly created profile when
- * the provisioning of a managed profile has completed successfully.
+ * the provisioning of a managed profile has completed successfully. It is used in both the
+ * Profile Owner and the Device Owner provisioning.
*
- * <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>The broadcast is limited to the DeviceAdminReceiver component specified in the message
+ * that started the provisioning. It is also limited to the managed profile.
*
* <p>Input: Nothing.</p>
* <p>Output: Nothing</p>
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 24bb2cc..24a354f 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16,8 +16,6 @@
package android.app.admin;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.ComponentName;
@@ -27,6 +25,7 @@ import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.RestrictionsManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Process;
@@ -34,11 +33,15 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
import android.service.trust.TrustAgentService;
import android.util.Log;
import com.android.org.conscrypt.TrustedCertificateStore;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
@@ -101,6 +104,17 @@ public class DevicePolicyManager {
= "android.app.action.ACTION_PROVISION_MANAGED_PROFILE";
/**
+ * A broadcast intent with this action can be sent to ManagedProvisionning to specify that the
+ * user has already consented to the creation of the managed profile.
+ * The intent must contain the extras
+ * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} and
+ * {@link #EXTRA_PROVISIONING_TOKEN}
+ * @hide
+ */
+ public static final String ACTION_PROVISIONING_USER_HAS_CONSENTED
+ = "android.app.action.USER_HAS_CONSENTED";
+
+ /**
* 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}.
@@ -109,6 +123,13 @@ public class DevicePolicyManager {
= "deviceAdminPackageName";
/**
+ * An int extra used to identify the consent of the user to create the managed profile.
+ * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}
+ */
+ public static final String EXTRA_PROVISIONING_TOKEN
+ = "android.app.extra.token";
+
+ /**
* 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}
@@ -174,15 +195,16 @@ public class DevicePolicyManager {
public static final String ACTION_SET_NEW_PASSWORD
= "android.app.action.SET_NEW_PASSWORD";
/**
- * Flag for {@link #addForwardingIntentFilter}: the intents will forwarded to the primary user.
+ * Flag used by {@link #addCrossProfileIntentFilter} to allow access of certain intents from a
+ * managed profile to its parent.
*/
- public static int FLAG_TO_PRIMARY_USER = 0x0001;
+ public static int FLAG_PARENT_CAN_ACCESS_MANAGED = 0x0001;
/**
- * Flag for {@link #addForwardingIntentFilter}: the intents will be forwarded to the managed
- * profile.
+ * Flag used by {@link #addCrossProfileIntentFilter} to allow access of certain intents from the
+ * parent to its managed profile.
*/
- public static int FLAG_TO_MANAGED_PROFILE = 0x0002;
+ public static int FLAG_MANAGED_CAN_ACCESS_PARENT = 0x0002;
/**
* Return true if the given administrator component is currently
@@ -359,8 +381,8 @@ public class DevicePolicyManager {
}
/**
- * Retrieve the current minimum password quality for all admins
- * or a particular one.
+ * Retrieve the current minimum password quality for all admins of this user
+ * and its profiles or a particular one.
* @param admin The name of the admin component to check, or null to aggregate
* all admins.
*/
@@ -412,8 +434,8 @@ public class DevicePolicyManager {
}
/**
- * Retrieve the current minimum password length for all admins
- * or a particular one.
+ * Retrieve the current minimum password length for all admins of this
+ * user and its profiles or a particular one.
* @param admin The name of the admin component to check, or null to aggregate
* all admins.
*/
@@ -467,8 +489,9 @@ public class DevicePolicyManager {
/**
* Retrieve the current number of upper case letters required in the
- * password for all admins or a particular one. This is the same value as
- * set by {#link {@link #setPasswordMinimumUpperCase(ComponentName, int)}
+ * password for all admins of this user and its profiles or a particular one.
+ * This is the same value as set by
+ * {#link {@link #setPasswordMinimumUpperCase(ComponentName, int)}
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
@@ -527,8 +550,9 @@ public class DevicePolicyManager {
/**
* Retrieve the current number of lower case letters required in the
- * password for all admins or a particular one. This is the same value as
- * set by {#link {@link #setPasswordMinimumLowerCase(ComponentName, int)}
+ * password for all admins of this user and its profiles or a particular one.
+ * This is the same value as set by
+ * {#link {@link #setPasswordMinimumLowerCase(ComponentName, int)}
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
@@ -644,8 +668,9 @@ public class DevicePolicyManager {
/**
* Retrieve the current number of numerical digits required in the password
- * for all admins or a particular one. This is the same value as
- * set by {#link {@link #setPasswordMinimumNumeric(ComponentName, int)}
+ * for all admins of this user and its profiles or a particular one.
+ * This is the same value as set by
+ * {#link {@link #setPasswordMinimumNumeric(ComponentName, int)}
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
@@ -760,8 +785,9 @@ public class DevicePolicyManager {
/**
* Retrieve the current number of non-letter characters required in the
- * password for all admins or a particular one. This is the same value as
- * set by {#link {@link #setPasswordMinimumNonLetter(ComponentName, int)}
+ * password for all admins of this user and its profiles or a particular one.
+ * This is the same value as set by
+ * {#link {@link #setPasswordMinimumNonLetter(ComponentName, int)}
* and only applies when the password quality is
* {@link #PASSWORD_QUALITY_COMPLEX}.
*
@@ -868,9 +894,10 @@ public class DevicePolicyManager {
/**
* Get the current password expiration time for the given admin or an aggregate of
- * all admins if admin is null. If the password is expired, this will return the time since
- * the password expired as a negative number. If admin is null, then a composite of all
- * expiration timeouts is returned - which will be the minimum of all timeouts.
+ * all admins of this user and its profiles if admin is null. If the password is
+ * expired, this will return the time since the password expired as a negative number.
+ * If admin is null, then a composite of all expiration timeouts is returned
+ * - which will be the minimum of all timeouts.
*
* @param admin The name of the admin component to check, or null to aggregate all admins.
* @return The password expiration time, in ms.
@@ -887,8 +914,8 @@ public class DevicePolicyManager {
}
/**
- * Retrieve the current password history length for all admins
- * or a particular one.
+ * Retrieve the current password history length for all admins of this
+ * user and its profiles or a particular one.
* @param admin The name of the admin component to check, or null to aggregate
* all admins.
* @return The length of the password history
@@ -923,14 +950,13 @@ public class DevicePolicyManager {
/**
* Determine whether the current password the user has set is sufficient
* to meet the policy requirements (quality, minimum length) that have been
- * requested.
+ * requested by the admins of this user and its profiles.
*
* <p>The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
* this method; if it has not, a security exception will be thrown.
*
- * @return Returns true if the password meets the current requirements,
- * else false.
+ * @return Returns true if the password meets the current requirements, else false.
*/
public boolean isActivePasswordSufficient() {
if (mService != null) {
@@ -993,7 +1019,7 @@ public class DevicePolicyManager {
/**
* Retrieve the current maximum number of login attempts that are allowed
- * before the device wipes itself, for all admins
+ * before the device wipes itself, for all admins of this user and its profiles
* or a particular one.
* @param admin The name of the admin component to check, or null to aggregate
* all admins.
@@ -1037,6 +1063,8 @@ public class DevicePolicyManager {
* {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} to be able to call
* this method; if it has not, a security exception will be thrown.
*
+ * Can not be called from a managed profile.
+ *
* @param password The new password for the user.
* @param flags May be 0 or {@link #RESET_PASSWORD_REQUIRE_ENTRY}.
* @return Returns true if the password was applied, or false if it is
@@ -1077,8 +1105,8 @@ public class DevicePolicyManager {
}
/**
- * Retrieve the current maximum time to unlock for all admins
- * or a particular one.
+ * Retrieve the current maximum time to unlock for all admins of this user
+ * and its profiles or a particular one.
* @param admin The name of the admin component to check, or null to aggregate
* all admins.
*/
@@ -1943,17 +1971,16 @@ public class DevicePolicyManager {
}
/**
- * 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.
+ * Called by the profile owner so that some intents sent in the managed profile can also be
+ * resolved in the parent, or vice versa.
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @param filter if an intent matches this IntentFilter, then it can be forwarded.
+ * @param filter The {@link IntentFilter} the intent has to match to be also resolved in the
+ * other profile
*/
- public void addForwardingIntentFilter(ComponentName admin, IntentFilter filter, int flags) {
+ public void addCrossProfileIntentFilter(ComponentName admin, IntentFilter filter, int flags) {
if (mService != null) {
try {
- mService.addForwardingIntentFilter(admin, filter, flags);
+ mService.addCrossProfileIntentFilter(admin, filter, flags);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1961,14 +1988,14 @@ public class DevicePolicyManager {
}
/**
- * Called by a profile owner to remove the forwarding intent filters from the current user
- * and from the owner.
+ * Called by a profile owner to remove the cross-profile intent filters from the managed profile
+ * and from the parent.
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
*/
- public void clearForwardingIntentFilters(ComponentName admin) {
+ public void clearCrossProfileIntentFilters(ComponentName admin) {
if (mService != null) {
try {
- mService.clearForwardingIntentFilters(admin);
+ mService.clearCrossProfileIntentFilters(admin);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1976,6 +2003,43 @@ public class DevicePolicyManager {
}
/**
+ * Called by a device owner to create a user with the specified name. The UserHandle returned
+ * by this method should not be persisted as user handles are recycled as users are removed and
+ * created. If you need to persist an identifier for this user, use
+ * {@link UserManager#getSerialNumberForUser}.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param name the user's name
+ * @see UserHandle
+ * @return the UserHandle object for the created user, or null if the user could not be created.
+ */
+ public UserHandle createUser(ComponentName admin, String name) {
+ try {
+ return mService.createUser(admin, name);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not create a user", re);
+ }
+ return null;
+ }
+
+ /**
+ * Called by a device owner to remove a user and all associated data. The primary user can
+ * not be removed.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param userHandle the user to remove.
+ * @return {@code true} if the user was removed, {@code false} otherwise.
+ */
+ public boolean removeUser(ComponentName admin, UserHandle userHandle) {
+ try {
+ return mService.removeUser(admin, userHandle);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not remove user ", re);
+ return false;
+ }
+ }
+
+ /**
* Called by a profile or device owner to get the application restrictions for a given target
* application running in the managed profile.
*
@@ -2044,64 +2108,90 @@ public class DevicePolicyManager {
}
/**
- * Called by profile or device owner to re-enable a system app that was disabled by default
- * when the managed profile was created. This should only be called from a profile or device
- * owner running within a managed profile.
+ * Called by device or profile owner to block or unblock packages. When a package is blocked it
+ * is unavailable for use, but the data and actual package file remain.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @param packageName The package to be re-enabled in the current profile.
+ * @param packageName The name of the package to block or unblock.
+ * @param blocked {@code true} if the package should be blocked, {@code false} if it should be
+ * unblocked.
+ * @return boolean Whether the blocked setting of the package was successfully updated.
*/
- public void enableSystemApp(ComponentName admin, String packageName) {
+ public boolean setApplicationBlocked(ComponentName admin, String packageName,
+ boolean blocked) {
if (mService != null) {
try {
- mService.enableSystemApp(admin, packageName);
+ return mService.setApplicationBlocked(admin, packageName, blocked);
} catch (RemoteException e) {
- Log.w(TAG, "Failed to install package: " + packageName);
+ Log.w(TAG, "Failed talking with device policy service", e);
}
}
+ return false;
}
/**
- * Called by a profile owner to disable account management for a specific type of account.
+ * Called by profile or device owner to block or unblock currently installed packages. This
+ * should only be called by a profile or device owner running within a managed profile.
*
- * <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 intent An intent matching the app(s) to be updated. All apps that resolve for this
+ * intent will be updated in the current profile.
+ * @param blocked {@code true} if the packages should be blocked, {@code false} if they should
+ * be unblocked.
+ * @return int The number of activities that matched the intent and were updated.
+ */
+ public int setApplicationsBlocked(ComponentName admin, Intent intent, boolean blocked) {
+ if (mService != null) {
+ try {
+ return mService.setApplicationsBlocked(admin, intent, blocked);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Called by device or profile owner to determine if a package is blocked.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @param accountType For which account management is disabled or enabled.
- * @param disabled The boolean indicating that account management will be disabled (true) or
- * enabled (false).
+ * @param packageName The name of the package to retrieve the blocked status of.
+ * @return boolean {@code true} if the package is blocked, {@code false} otherwise.
*/
- public void setAccountManagementDisabled(ComponentName admin, String accountType,
- boolean disabled) {
+ public boolean isApplicationBlocked(ComponentName admin, String packageName) {
if (mService != null) {
try {
- mService.setAccountManagementDisabled(admin, accountType, disabled);
+ return mService.isApplicationBlocked(admin, packageName);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
}
+ return false;
}
/**
- * Called by profile or device owner to re-enable system apps by intent that were disabled
- * by default when the managed profile was created. This should only be called from a profile
- * or device owner running within a managed profile.
+ * Called by a profile owner to disable account management for a specific type of account.
+ *
+ * <p>The calling device admin must be a profile owner. If it is not, a
+ * security exception will be thrown.
+ *
+ * <p>When account management is disabled for an account type, adding or removing an account
+ * of that type will not be possible.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @param intent An intent matching the app(s) to be installed. All apps that resolve for this
- * intent will be re-enabled in the current profile.
- * @return int The number of activities that matched the intent and were installed.
+ * @param accountType For which account management is disabled or enabled.
+ * @param disabled The boolean indicating that account management will be disabled (true) or
+ * enabled (false).
*/
- public int enableSystemApp(ComponentName admin, Intent intent) {
+ public void setAccountManagementDisabled(ComponentName admin, String accountType,
+ boolean disabled) {
if (mService != null) {
try {
- return mService.enableSystemAppWithIntent(admin, intent);
+ mService.setAccountManagementDisabled(admin, accountType, disabled);
} catch (RemoteException e) {
- Log.w(TAG, "Failed to install packages matching filter: " + intent);
+ Log.w(TAG, "Failed talking with device policy service", e);
}
}
- return 0;
}
/**
@@ -2172,4 +2262,61 @@ public class DevicePolicyManager {
}
return false;
}
+
+ /**
+ * Called by device owners to update {@link Settings.Global} settings. Validation that the value
+ * of the setting is in the correct form for the setting type should be performed by the caller.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param setting The name of the setting to update.
+ * @param value The value to update the setting to.
+ */
+ public void setGlobalSetting(ComponentName admin, String setting, String value) {
+ if (mService != null) {
+ try {
+ mService.setGlobalSetting(admin, setting, value);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Called by profile or device owners to update {@link Settings.Secure} settings. Validation
+ * that the value of the setting is in the correct form for the setting type should be performed
+ * by the caller.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param setting The name of the setting to update.
+ * @param value The value to update the setting to.
+ */
+ public void setSecureSetting(ComponentName admin, String setting, String value) {
+ if (mService != null) {
+ try {
+ mService.setSecureSetting(admin, setting, value);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Designates a specific broadcast receiver component as the provider for
+ * making permission requests of a local or remote administrator of the user.
+ * <p/>
+ * Only a profile owner can designate the restrictions provider.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param receiver The component name of a BroadcastReceiver that handles the
+ * {@link RestrictionsManager#ACTION_REQUEST_PERMISSION} intent. If this param is null,
+ * it removes the restrictions provider previously assigned.
+ */
+ public void setRestrictionsProvider(ComponentName admin, ComponentName receiver) {
+ if (mService != null) {
+ try {
+ mService.setRestrictionsProvider(admin, receiver);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to set permission provider on device policy service");
+ }
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 03ced0f..7d7a312 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -22,6 +22,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.RemoteCallback;
+import android.os.UserHandle;
/**
* Internal IPC interface to the device policy service.
@@ -120,12 +121,19 @@ interface IDevicePolicyManager {
void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings);
Bundle getApplicationRestrictions(in ComponentName who, in String packageName);
+ void setRestrictionsProvider(in ComponentName who, in ComponentName provider);
+ ComponentName getRestrictionsProvider(int userHandle);
+
void setUserRestriction(in ComponentName who, in String key, boolean enable);
- void addForwardingIntentFilter(in ComponentName admin, in IntentFilter filter, int flags);
- void clearForwardingIntentFilters(in ComponentName admin);
+ void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags);
+ void clearCrossProfileIntentFilters(in ComponentName admin);
+
+ boolean setApplicationBlocked(in ComponentName admin, in String packageName, boolean blocked);
+ int setApplicationsBlocked(in ComponentName admin, in Intent intent, boolean blocked);
+ boolean isApplicationBlocked(in ComponentName admin, in String packageName);
- void enableSystemApp(in ComponentName admin, in String packageName);
- int enableSystemAppWithIntent(in ComponentName admin, in Intent intent);
+ UserHandle createUser(in ComponentName who, in String name);
+ boolean removeUser(in ComponentName who, in UserHandle userHandle);
void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled);
String[] getAccountTypesWithManagementDisabled();
@@ -133,4 +141,7 @@ interface IDevicePolicyManager {
void setLockTaskComponents(in ComponentName[] components);
ComponentName[] getLockTaskComponents();
boolean isLockTaskPermitted(in ComponentName component);
+
+ void setGlobalSetting(in ComponentName who, in String setting, in String value);
+ void setSecureSetting(in ComponentName who, in String setting, in String value);
}
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
new file mode 100644
index 0000000..46f082e
--- /dev/null
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -0,0 +1,415 @@
+/*
+ * 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.backup;
+
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Concrete class that provides a stable-API bridge between IBackupTransport
+ * and its implementations.
+ *
+ * @hide
+ */
+public class BackupTransport {
+ public static final int TRANSPORT_OK = 0;
+ public static final int TRANSPORT_ERROR = 1;
+ public static final int TRANSPORT_NOT_INITIALIZED = 2;
+ public static final int TRANSPORT_PACKAGE_REJECTED = 3;
+ public static final int AGENT_ERROR = 4;
+ public static final int AGENT_UNKNOWN = 5;
+
+ IBackupTransport mBinderImpl = new TransportImpl();
+ /** @hide */
+ public IBinder getBinder() {
+ return mBinderImpl.asBinder();
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Transport self-description and general configuration interfaces
+ //
+
+ /**
+ * Ask the transport for the name under which it should be registered. This will
+ * typically be its host service's component name, but need not be.
+ */
+ public String name() {
+ throw new UnsupportedOperationException("Transport name() not implemented");
+ }
+
+ /**
+ * Ask the transport for an Intent that can be used to launch any internal
+ * configuration Activity that it wishes to present. For example, the transport
+ * may offer a UI for allowing the user to supply login credentials for the
+ * transport's off-device backend.
+ *
+ * If the transport does not supply any user-facing configuration UI, it should
+ * return null from this method.
+ *
+ * @return An Intent that can be passed to Context.startActivity() in order to
+ * launch the transport's configuration UI. This method will return null
+ * if the transport does not offer any user-facing configuration UI.
+ */
+ public Intent configurationIntent() {
+ return null;
+ }
+
+ /**
+ * On demand, supply a one-line string that can be shown to the user that
+ * describes the current backend destination. For example, a transport that
+ * can potentially associate backup data with arbitrary user accounts should
+ * include the name of the currently-active account here.
+ *
+ * @return A string describing the destination to which the transport is currently
+ * sending data. This method should not return null.
+ */
+ public String currentDestinationString() {
+ throw new UnsupportedOperationException(
+ "Transport currentDestinationString() not implemented");
+ }
+
+ /**
+ * Ask the transport where, on local device storage, to keep backup state blobs.
+ * This is per-transport so that mock transports used for testing can coexist with
+ * "live" backup services without interfering with the live bookkeeping. The
+ * returned string should be a name that is expected to be unambiguous among all
+ * available backup transports; the name of the class implementing the transport
+ * is a good choice.
+ *
+ * @return A unique name, suitable for use as a file or directory name, that the
+ * Backup Manager could use to disambiguate state files associated with
+ * different backup transports.
+ */
+ public String transportDirName() {
+ throw new UnsupportedOperationException(
+ "Transport transportDirName() not implemented");
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Device-level operations common to both key/value and full-data storage
+
+ /**
+ * Initialize the server side storage for this device, erasing all stored data.
+ * The transport may send the request immediately, or may buffer it. After
+ * this is called, {@link #finishBackup} will be called to ensure the request
+ * is sent and received successfully.
+ *
+ * @return One of {@link BackupTransport#TRANSPORT_OK} (OK so far) or
+ * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure).
+ */
+ public int initializeDevice() {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ /**
+ * Erase the given application's data from the backup destination. This clears
+ * out the given package's data from the current backup set, making it as though
+ * the app had never yet been backed up. After this is called, {@link finishBackup}
+ * must be called to ensure that the operation is recorded successfully.
+ *
+ * @return the same error codes as {@link #performBackup}.
+ */
+ public int clearBackupData(PackageInfo packageInfo) {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ /**
+ * Finish sending application data to the backup destination. This must be
+ * called after {@link #performBackup}, {@link #performFullBackup}, or {@link clearBackupData}
+ * to ensure that all data is sent and the operation properly finalized. Only when this
+ * method returns true can a backup be assumed to have succeeded.
+ *
+ * @return the same error codes as {@link #performBackup} or {@link #performFullBackup}.
+ */
+ public int finishBackup() {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Key/value incremental backup support interfaces
+
+ /**
+ * Verify that this is a suitable time for a key/value backup pass. This should return zero
+ * if a backup is reasonable right now, some positive value otherwise. This method
+ * will be called outside of the {@link #performBackup}/{@link #finishBackup} pair.
+ *
+ * <p>If this is not a suitable time for a backup, the transport should return a
+ * backoff delay, in milliseconds, after which the Backup Manager should try again.
+ *
+ * @return Zero if this is a suitable time for a backup pass, or a positive time delay
+ * in milliseconds to suggest deferring the backup pass for a while.
+ */
+ public long requestBackupTime() {
+ return 0;
+ }
+
+ /**
+ * Send one application's key/value data update to the backup destination. The
+ * transport may send the data immediately, or may buffer it. After this is called,
+ * {@link #finishBackup} will be called to ensure the data is sent and recorded successfully.
+ *
+ * @param packageInfo The identity of the application whose data is being backed up.
+ * This specifically includes the signature list for the package.
+ * @param data The data stream that resulted from invoking the application's
+ * BackupService.doBackup() method. This may be a pipe rather than a file on
+ * persistent media, so it may not be seekable.
+ * @param wipeAllFirst When true, <i>all</i> backed-up data for the current device/account
+ * must be erased prior to the storage of the data provided here. The purpose of this
+ * is to provide a guarantee that no stale data exists in the restore set when the
+ * device begins providing incremental backups.
+ * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
+ * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), or
+ * {@link BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
+ * become lost due to inactivity purge or some other reason and needs re-initializing)
+ */
+ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd) {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Key/value dataset restore interfaces
+
+ /**
+ * Get the set of all backups currently available over this transport.
+ *
+ * @return Descriptions of the set of restore images available for this device,
+ * or null if an error occurred (the attempt should be rescheduled).
+ **/
+ public RestoreSet[] getAvailableRestoreSets() {
+ return null;
+ }
+
+ /**
+ * Get the identifying token of the backup set currently being stored from
+ * this device. This is used in the case of applications wishing to restore
+ * their last-known-good data.
+ *
+ * @return A token that can be passed to {@link #startRestore}, or 0 if there
+ * is no backup set available corresponding to the current device state.
+ */
+ public long getCurrentRestoreSet() {
+ return 0;
+ }
+
+ /**
+ * Start restoring application data from backup. After calling this function,
+ * alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData}
+ * to walk through the actual application data.
+ *
+ * @param token A backup token as returned by {@link #getAvailableRestoreSets}
+ * or {@link #getCurrentRestoreSet}.
+ * @param packages List of applications to restore (if data is available).
+ * Application data will be restored in the order given.
+ * @return One of {@link BackupTransport#TRANSPORT_OK} (OK so far, call
+ * {@link #nextRestorePackage}) or {@link BackupTransport#TRANSPORT_ERROR}
+ * (an error occurred, the restore should be aborted and rescheduled).
+ */
+ public int startRestore(long token, PackageInfo[] packages) {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ /**
+ * Get the package name of the next application with data in the backup store.
+ *
+ * @return The name of one of the packages supplied to {@link #startRestore},
+ * or "" (the empty string) if no more backup data is available,
+ * or null if an error occurred (the restore should be aborted and rescheduled).
+ */
+ public String nextRestorePackage() {
+ return null;
+ }
+
+ /**
+ * Get the data for the application returned by {@link #nextRestorePackage}.
+ * @param data An open, writable file into which the backup data should be stored.
+ * @return the same error codes as {@link #startRestore}.
+ */
+ public int getRestoreData(ParcelFileDescriptor outFd) {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ /**
+ * End a restore session (aborting any in-process data transfer as necessary),
+ * freeing any resources and connections used during the restore process.
+ */
+ public void finishRestore() {
+ throw new UnsupportedOperationException(
+ "Transport finishRestore() not implemented");
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Full backup interfaces
+
+ /**
+ * Verify that this is a suitable time for a full-data backup pass. This should return zero
+ * if a backup is reasonable right now, some positive value otherwise. This method
+ * will be called outside of the {@link #performFullBackup}/{@link #finishBackup} pair.
+ *
+ * <p>If this is not a suitable time for a backup, the transport should return a
+ * backoff delay, in milliseconds, after which the Backup Manager should try again.
+ *
+ * @return Zero if this is a suitable time for a backup pass, or a positive time delay
+ * in milliseconds to suggest deferring the backup pass for a while.
+ *
+ * @see #requestBackupTime()
+ */
+ public long requestFullBackupTime() {
+ return 0;
+ }
+
+ /**
+ * Begin the process of sending an application's full-data archive to the backend.
+ * The description of the package whose data will be delivered is provided, as well as
+ * the socket file descriptor on which the transport will receive the data itself.
+ *
+ * <p>If the package is not eligible for backup, the transport should return
+ * {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED}. In this case the system will
+ * simply proceed with the next candidate if any, or finish the full backup operation
+ * if all apps have been processed.
+ *
+ * <p>After the transport returns {@link BackupTransport#TRANSPORT_OK} from this
+ * method, the OS will proceed to call {@link #sendBackupData()} one or more times
+ * to deliver the application's data as a streamed tarball. The transport should not
+ * read() from the socket except as instructed to via the {@link #sendBackupData(int)}
+ * method.
+ *
+ * <p>After all data has been delivered to the transport, the system will call
+ * {@link #finishBackup()}. At this point the transport should commit the data to
+ * its datastore, if appropriate, and close the socket that had been provided in
+ * {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}.
+ *
+ * @param targetPackage The package whose data is to follow.
+ * @param socket The socket file descriptor through which the data will be provided.
+ * If the transport returns {@link #TRANSPORT_PACKAGE_REJECTED} here, it must still
+ * close this file descriptor now; otherwise it should be cached for use during
+ * succeeding calls to {@link #sendBackupData(int)}, and closed in response to
+ * {@link #finishBackup()}.
+ * @return TRANSPORT_PACKAGE_REJECTED to indicate that the stated application is not
+ * to be backed up; TRANSPORT_OK to indicate that the OS may proceed with delivering
+ * backup data; TRANSPORT_ERROR to indicate a fatal error condition that precludes
+ * performing a backup at this time.
+ */
+ public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) {
+ return BackupTransport.TRANSPORT_PACKAGE_REJECTED;
+ }
+
+ /**
+ * Tells the transport to read {@code numBytes} bytes of data from the socket file
+ * descriptor provided in the {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}
+ * call, and deliver those bytes to the datastore.
+ *
+ * @param numBytes The number of bytes of tarball data available to be read from the
+ * socket.
+ * @return TRANSPORT_OK on successful processing of the data; TRANSPORT_ERROR to
+ * indicate a fatal error situation. If an error is returned, the system will
+ * call finishBackup() and stop attempting backups until after a backoff and retry
+ * interval.
+ */
+ public int sendBackupData(int numBytes) {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ /**
+ * Bridge between the actual IBackupTransport implementation and the stable API. If the
+ * binder interface needs to change, we use this layer to translate so that we can
+ * (if appropriate) decouple those framework-side changes from the BackupTransport
+ * implementations.
+ */
+ class TransportImpl extends IBackupTransport.Stub {
+
+ @Override
+ public String name() throws RemoteException {
+ return BackupTransport.this.name();
+ }
+
+ @Override
+ public Intent configurationIntent() throws RemoteException {
+ return BackupTransport.this.configurationIntent();
+ }
+
+ @Override
+ public String currentDestinationString() throws RemoteException {
+ return BackupTransport.this.currentDestinationString();
+ }
+
+ @Override
+ public String transportDirName() throws RemoteException {
+ return BackupTransport.this.transportDirName();
+ }
+
+ @Override
+ public long requestBackupTime() throws RemoteException {
+ return BackupTransport.this.requestBackupTime();
+ }
+
+ @Override
+ public int initializeDevice() throws RemoteException {
+ return BackupTransport.this.initializeDevice();
+ }
+
+ @Override
+ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd)
+ throws RemoteException {
+ return BackupTransport.this.performBackup(packageInfo, inFd);
+ }
+
+ @Override
+ public int clearBackupData(PackageInfo packageInfo) throws RemoteException {
+ return BackupTransport.this.clearBackupData(packageInfo);
+ }
+
+ @Override
+ public int finishBackup() throws RemoteException {
+ return BackupTransport.this.finishBackup();
+ }
+
+ @Override
+ public RestoreSet[] getAvailableRestoreSets() throws RemoteException {
+ return BackupTransport.this.getAvailableRestoreSets();
+ }
+
+ @Override
+ public long getCurrentRestoreSet() throws RemoteException {
+ return BackupTransport.this.getCurrentRestoreSet();
+ }
+
+ @Override
+ public int startRestore(long token, PackageInfo[] packages) throws RemoteException {
+ return BackupTransport.this.startRestore(token, packages);
+ }
+
+ @Override
+ public String nextRestorePackage() throws RemoteException {
+ return BackupTransport.this.nextRestorePackage();
+ }
+
+ @Override
+ public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
+ return BackupTransport.this.getRestoreData(outFd);
+ }
+
+ @Override
+ public void finishRestore() throws RemoteException {
+ BackupTransport.this.finishRestore();
+ }
+ }
+}
diff --git a/core/java/android/app/task/ITaskCallback.aidl b/core/java/android/app/task/ITaskCallback.aidl
index ffa57d1..d8a32fd 100644
--- a/core/java/android/app/task/ITaskCallback.aidl
+++ b/core/java/android/app/task/ITaskCallback.aidl
@@ -34,14 +34,17 @@ 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.
+ * @param ongoing True to indicate that the client is processing the task. False if the task is
+ * complete
*/
- void acknowledgeStartMessage(int taskId);
+ void acknowledgeStartMessage(int taskId, boolean ongoing);
/**
* Immediate callback to the system after sending a stop signal, used to quickly detect ANR.
*
* @param taskId Unique integer used to identify this task.
+ * @param rescheulde Whether or not to reschedule this task.
*/
- void acknowledgeStopMessage(int taskId);
+ void acknowledgeStopMessage(int taskId, boolean reschedule);
/*
* 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.
diff --git a/core/java/android/app/task/ITaskManager.aidl b/core/java/android/app/task/ITaskManager.aidl
new file mode 100644
index 0000000..b56c78a
--- /dev/null
+++ b/core/java/android/app/task/ITaskManager.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.app.task;
+
+import android.app.task.Task;
+
+ /**
+ * IPC interface that supports the app-facing {@link #TaskManager} api.
+ * {@hide}
+ */
+interface ITaskManager {
+ int schedule(in Task task);
+ void cancel(int taskId);
+ void cancelAll();
+ List<Task> getAllPendingTasks();
+}
diff --git a/core/java/android/tv/TvInputInfo.aidl b/core/java/android/app/task/Task.aidl
index abc4b47..1f25439 100644
--- a/core/java/android/tv/TvInputInfo.aidl
+++ b/core/java/android/app/task/Task.aidl
@@ -1,4 +1,4 @@
-/*
+/**
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,6 +14,7 @@
* limitations under the License.
*/
-package android.tv;
+package android.app.task;
-parcelable TvInputInfo;
+parcelable Task;
+ \ No newline at end of file
diff --git a/core/java/android/content/Task.java b/core/java/android/app/task/Task.java
index ed5ed88..ca4aeb2 100644
--- a/core/java/android/content/Task.java
+++ b/core/java/android/app/task/Task.java
@@ -14,23 +14,26 @@
* limitations under the License
*/
-package android.content;
+package android.app.task;
-import android.app.task.TaskService;
+import android.content.ComponentName;
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
+ * Container of data passed to the {@link android.app.task.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;
+ /** Default. */
+ public final int NONE = 0;
+ /** This task requires network connectivity. */
+ public final int ANY = 1;
+ /** This task requires network connectivity that is unmetered. */
+ public final int UNMETERED = 2;
}
/**
@@ -42,10 +45,26 @@ public class Task implements Parcelable {
public final int EXPONENTIAL = 1;
}
+ private final int taskId;
+ // TODO: Change this to use PersistableBundle when that lands in master.
+ private final Bundle extras;
+ private final ComponentName service;
+ private final boolean requireCharging;
+ private final boolean requireDeviceIdle;
+ private final boolean hasEarlyConstraint;
+ private final boolean hasLateConstraint;
+ 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;
+
/**
* Unique task id associated with this class. This is assigned to your task by the scheduler.
*/
- public int getTaskId() {
+ public int getId() {
return taskId;
}
@@ -59,8 +78,8 @@ public class Task implements Parcelable {
/**
* Name of the service endpoint that will be called back into by the TaskManager.
*/
- public String getServiceClassName() {
- return serviceClassName;
+ public ComponentName getService() {
+ return service;
}
/**
@@ -78,7 +97,7 @@ public class Task implements Parcelable {
}
/**
- * See {@link android.content.Task.NetworkType} for a description of this value.
+ * See {@link android.app.task.Task.NetworkType} for a description of this value.
*/
public int getNetworkCapabilities() {
return networkCapabilities;
@@ -125,31 +144,35 @@ public class Task implements Parcelable {
}
/**
- * See {@link android.content.Task.BackoffPolicy} for an explanation of the values this field
+ * See {@link android.app.task.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;
+ /**
+ * User can specify an early constraint of 0L, which is valid, so we keep track of whether the
+ * function was called at all.
+ * @hide
+ */
+ public boolean hasEarlyConstraint() {
+ return hasEarlyConstraint;
+ }
+
+ /**
+ * User can specify a late constraint of 0L, which is valid, so we keep track of whether the
+ * function was called at all.
+ * @hide
+ */
+ public boolean hasLateConstraint() {
+ return hasLateConstraint;
+ }
private Task(Parcel in) {
taskId = in.readInt();
extras = in.readBundle();
- serviceClassName = in.readString();
+ service = ComponentName.readFromParcel(in);
requireCharging = in.readInt() == 1;
requireDeviceIdle = in.readInt() == 1;
networkCapabilities = in.readInt();
@@ -159,12 +182,14 @@ public class Task implements Parcelable {
intervalMillis = in.readLong();
initialBackoffMillis = in.readLong();
backoffPolicy = in.readInt();
+ hasEarlyConstraint = in.readInt() == 1;
+ hasLateConstraint = in.readInt() == 1;
}
private Task(Task.Builder b) {
taskId = b.mTaskId;
extras = new Bundle(b.mExtras);
- serviceClassName = b.mTaskServiceClassName;
+ service = b.mTaskService;
requireCharging = b.mRequiresCharging;
requireDeviceIdle = b.mRequiresDeviceIdle;
networkCapabilities = b.mNetworkCapabilities;
@@ -174,6 +199,8 @@ public class Task implements Parcelable {
intervalMillis = b.mIntervalMillis;
initialBackoffMillis = b.mInitialBackoffMillis;
backoffPolicy = b.mBackoffPolicy;
+ hasEarlyConstraint = b.mHasEarlyConstraint;
+ hasLateConstraint = b.mHasLateConstraint;
}
@Override
@@ -185,7 +212,7 @@ public class Task implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
out.writeInt(taskId);
out.writeBundle(extras);
- out.writeString(serviceClassName);
+ ComponentName.writeToParcel(service, out);
out.writeInt(requireCharging ? 1 : 0);
out.writeInt(requireDeviceIdle ? 1 : 0);
out.writeInt(networkCapabilities);
@@ -195,6 +222,8 @@ public class Task implements Parcelable {
out.writeLong(intervalMillis);
out.writeLong(initialBackoffMillis);
out.writeInt(backoffPolicy);
+ out.writeInt(hasEarlyConstraint ? 1 : 0);
+ out.writeInt(hasLateConstraint ? 1 : 0);
}
public static final Creator<Task> CREATOR = new Creator<Task>() {
@@ -212,10 +241,10 @@ public class Task implements Parcelable {
/**
* Builder class for constructing {@link Task} objects.
*/
- public final class Builder {
+ public static final class Builder {
private int mTaskId;
private Bundle mExtras;
- private String mTaskServiceClassName;
+ private ComponentName mTaskService;
// Requirements.
private boolean mRequiresCharging;
private boolean mRequiresDeviceIdle;
@@ -225,6 +254,8 @@ public class Task implements Parcelable {
private long mMaxExecutionDelayMillis;
// Periodic parameters.
private boolean mIsPeriodic;
+ private boolean mHasEarlyConstraint;
+ private boolean mHasLateConstraint;
private long mIntervalMillis;
// Back-off parameters.
private long mInitialBackoffMillis = 5000L;
@@ -236,11 +267,11 @@ public class Task implements Parcelable {
* @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
+ * @param taskService The endpoint that you implement that will receive the callback from the
* TaskManager.
*/
- public Builder(int taskId, Class<TaskService> cls) {
- mTaskServiceClassName = cls.getClass().getName();
+ public Builder(int taskId, ComponentName taskService) {
+ mTaskService = taskService;
mTaskId = taskId;
}
@@ -255,7 +286,7 @@ public class Task implements Parcelable {
/**
* 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}.
+ * will be a parameter defined in {@link android.app.task.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
@@ -296,7 +327,7 @@ public class Task implements Parcelable {
* 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
+ * persisted beyond boot 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.
@@ -307,6 +338,7 @@ public class Task implements Parcelable {
public Builder setPeriodic(long intervalMillis) {
mIsPeriodic = true;
mIntervalMillis = intervalMillis;
+ mHasEarlyConstraint = mHasLateConstraint = true;
return this;
}
@@ -314,12 +346,13 @@ public class Task implements Parcelable {
* 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.
+ * {@link android.app.task.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;
+ mHasEarlyConstraint = true;
return this;
}
@@ -328,10 +361,11 @@ public class Task implements Parcelable {
* 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.
+ * {@link android.app.task.Task.Builder#build()} is called.
*/
public Builder setOverrideDeadline(long maxExecutionDelayMillis) {
mMaxExecutionDelayMillis = maxExecutionDelayMillis;
+ mHasLateConstraint = true;
return this;
}
@@ -360,31 +394,18 @@ public class Task implements Parcelable {
* @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);
+ if (mExtras == null) {
+ mExtras = Bundle.EMPTY;
+ }
+ if (mTaskId < 0) {
+ throw new IllegalArgumentException("Task id must be greater than 0.");
}
// Check that a deadline was not set on a periodic task.
- if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) {
+ if (mIsPeriodic && mHasLateConstraint) {
throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
"periodic task.");
}
- if (mIsPeriodic && (mMinLatencyMillis != 0L)) {
+ if (mIsPeriodic && mHasEarlyConstraint) {
throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
"periodic task");
}
diff --git a/core/java/android/content/TaskManager.java b/core/java/android/app/task/TaskManager.java
index d28d78a..00f57da 100644
--- a/core/java/android/content/TaskManager.java
+++ b/core/java/android/app/task/TaskManager.java
@@ -14,14 +14,19 @@
* limitations under the License
*/
-package android.content;
+package android.app.task;
import java.util.List;
+import android.content.Context;
+
/**
* 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)}.
+ * <p>You do not
+ * instantiate this class directly; instead, retrieve it through
+ * {@link android.content.Context#getSystemService
+ * Context.getSystemService(Context.TASK_SERVICE)}.
*/
public abstract class TaskManager {
/*
@@ -29,18 +34,19 @@ public abstract class TaskManager {
* 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;
+ public static final int RESULT_FAILURE = 0;
/**
* 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;
+ public static final int RESULT_SUCCESS = 1;
- /*
- * @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.
+ /**
+ * @param task The task you wish scheduled. See
+ * {@link android.app.task.Task.Builder Task.Builder} for more detail on the sorts of tasks
+ * you can schedule.
+ * @return If >0, this int returns 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);
diff --git a/core/java/android/app/task/TaskParams.java b/core/java/android/app/task/TaskParams.java
index e2eafd8..dacb348 100644
--- a/core/java/android/app/task/TaskParams.java
+++ b/core/java/android/app/task/TaskParams.java
@@ -29,7 +29,14 @@ public class TaskParams implements Parcelable {
private final int taskId;
private final Bundle extras;
- private final IBinder mCallback;
+ private final IBinder callback;
+
+ /** @hide */
+ public TaskParams(int taskId, Bundle extras, IBinder callback) {
+ this.taskId = taskId;
+ this.extras = extras;
+ this.callback = callback;
+ }
/**
* @return The unique id of this task, specified at creation time.
@@ -40,24 +47,22 @@ public class TaskParams implements Parcelable {
/**
* @return The extras you passed in when constructing this task with
- * {@link android.content.Task.Builder#setExtras(android.os.Bundle)}. This will
+ * {@link android.app.task.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
- */
+ /** @hide */
public ITaskCallback getCallback() {
- return ITaskCallback.Stub.asInterface(mCallback);
+ return ITaskCallback.Stub.asInterface(callback);
}
private TaskParams(Parcel in) {
taskId = in.readInt();
extras = in.readBundle();
- mCallback = in.readStrongBinder();
+ callback = in.readStrongBinder();
}
@Override
@@ -69,7 +74,7 @@ public class TaskParams implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(taskId);
dest.writeBundle(extras);
- dest.writeStrongBinder(mCallback);
+ dest.writeStrongBinder(callback);
}
public static final Creator<TaskParams> CREATOR = new Creator<TaskParams>() {
diff --git a/core/java/android/app/task/TaskService.java b/core/java/android/app/task/TaskService.java
index 81333be..8ce4484 100644
--- a/core/java/android/app/task/TaskService.java
+++ b/core/java/android/app/task/TaskService.java
@@ -18,7 +18,6 @@ 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;
@@ -29,7 +28,7 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy;
/**
- * <p>Entry point for the callback from the {@link android.content.TaskManager}.</p>
+ * <p>Entry point for the callback from the {@link android.app.task.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>
@@ -124,22 +123,20 @@ public abstract class TaskService extends Service {
switch (msg.what) {
case MSG_EXECUTE_TASK:
try {
- TaskService.this.onStartTask(params);
+ boolean workOngoing = TaskService.this.onStartTask(params);
+ ackStartMessage(params, workOngoing);
} 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);
+ boolean ret = TaskService.this.onStopTask(params);
+ ackStopMessage(params, ret);
} 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:
@@ -162,30 +159,34 @@ public abstract class TaskService extends Service {
}
}
- /**
- * 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) {
+ private void ackStartMessage(TaskParams params, boolean workOngoing) {
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);
- }
+ callback.acknowledgeStartMessage(taskId, workOngoing);
} 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.");
+ Log.d(TAG, "Attempting to ack a task that has already been processed.");
+ }
+ }
+ }
+
+ private void ackStopMessage(TaskParams params, boolean reschedule) {
+ final ITaskCallback callback = params.getCallback();
+ final int taskId = params.getTaskId();
+ if (callback != null) {
+ try {
+ callback.acknowledgeStopMessage(taskId, reschedule);
+ } catch(RemoteException e) {
+ Log.e(TAG, "System unreachable for stopping task.");
+ }
+ } else {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Attempting to ack a task that has already been processed.");
}
}
}
@@ -203,47 +204,43 @@ public abstract class TaskService extends Service {
*
* @param params Parameters specifying info about this task, including the extras bundle you
* optionally provided at task-creation time.
+ * @return True if your service needs to process the work (on a separate thread). False if
+ * there's no more work to be done for this task.
*/
- public abstract void onStartTask(TaskParams params);
+ public abstract boolean onStartTask(TaskParams params);
/**
- * This method is called if your task should be stopped even before you've called
- * {@link #taskFinished(TaskParams, boolean)}.
+ * This method is called if the system has determined that you must stop execution of your task
+ * even before you've had a chance to call {@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
+ * {@link android.app.task.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
+ * {@link android.app.task.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>
+ * immediate repercussion is that the system will cease holding a wakelock for you.</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.
+ * on the retry 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
+ * Callback to inform the TaskManager you've finished executing. 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.
+ * 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.app.task.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
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index d3e9089..e5bf7d0 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -317,9 +317,9 @@ 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.
+ * Sent to an {@link AppWidgetProvider} after AppWidget state related to that 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:
*
@@ -343,7 +343,7 @@ public class AppWidgetManager {
* <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
+ * @see #ACTION_APPWIDGET_HOST_RESTORED
*/
public static final String ACTION_APPWIDGET_RESTORED
= "android.appwidget.action.APPWIDGET_RESTORED";
@@ -352,7 +352,7 @@ public class AppWidgetManager {
* 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.
+ * widget host instances, it will receive this broadcast separately for each one.
*
* <p>The intent will contain the following extras:
*
@@ -380,7 +380,7 @@ public class AppWidgetManager {
* <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
+ * @see #ACTION_APPWIDGET_RESTORED
*/
public static final String ACTION_APPWIDGET_HOST_RESTORED
= "android.appwidget.action.APPWIDGET_HOST_RESTORED";
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index e79deec..42c2aeb 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -18,6 +18,8 @@ package android.bluetooth;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.bluetooth.le.BluetoothLeScanner;
import android.content.Context;
import android.os.Handler;
import android.os.IBinder;
@@ -498,6 +500,34 @@ public final class BluetoothAdapter {
}
/**
+ * Returns a {@link BluetoothLeAdvertiser} object for Bluetooth LE Advertising operations.
+ */
+ public BluetoothLeAdvertiser getBluetoothLeAdvertiser() {
+ // TODO: Return null if this feature is not supported by hardware.
+ try {
+ IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
+ return new BluetoothLeAdvertiser(iGatt);
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to get BluetoothLeAdvertiser, error: " + e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns a {@link BluetoothLeScanner} object for Bluetooth LE scan operations.
+ */
+ public BluetoothLeScanner getBluetoothLeScanner() {
+ // TODO: Return null if BLE scan is not supported by hardware.
+ try {
+ IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
+ return new BluetoothLeScanner(iGatt);
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to get BluetoothLeScanner, error: " + e);
+ return null;
+ }
+ }
+
+ /**
* Interface for BLE advertising callback.
*
* @hide
@@ -2024,6 +2054,10 @@ public final class BluetoothAdapter {
}
}
+ @Override
+ public void onMultiAdvertiseCallback(int status) {
+ // no op
+ }
/**
* Callback reporting LE ATT MTU.
* @hide
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 601d9ee..c9df9c0 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -581,7 +581,15 @@ public final class BluetoothGatt implements BluetoothProfile {
public void onAdvertiseStateChange(int state, int status) {
if (DBG) Log.d(TAG, "onAdvertiseStateChange() - state = "
+ state + " status=" + status);
- }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void onMultiAdvertiseCallback(int status) {
+ // no op.
+ }
/**
* Callback invoked when the MTU for a given connection changes
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 1574090..d898060 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -104,6 +104,12 @@ public interface BluetoothProfile {
public static final int MAP = 9;
/**
+ * A2DP Sink Profile
+ * @hide
+ */
+ public static final int A2DP_SINK = 10;
+
+ /**
* Default priority for devices that we try to auto-connect to and
* and allow incoming connections for the profile
* @hide
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 00fd7ce..b98e5ae 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -81,8 +81,8 @@ import java.nio.ByteBuffer;
*/
public final class BluetoothSocket implements Closeable {
private static final String TAG = "BluetoothSocket";
- private static final boolean DBG = true;
- private static final boolean VDBG = false;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
/** @hide */
public static final int MAX_RFCOMM_CHANNEL = 30;
@@ -185,7 +185,7 @@ public final class BluetoothSocket implements Closeable {
BluetoothSocket as = new BluetoothSocket(this);
as.mSocketState = SocketState.CONNECTED;
FileDescriptor[] fds = mSocket.getAncillaryFileDescriptors();
- if (VDBG) Log.d(TAG, "socket fd passed by stack fds: " + fds);
+ if (DBG) Log.d(TAG, "socket fd passed by stack fds: " + fds);
if(fds == null || fds.length != 1) {
Log.e(TAG, "socket fd passed from stack failed, fds: " + fds);
as.close();
@@ -352,24 +352,24 @@ public final class BluetoothSocket implements Closeable {
// read out port number
try {
synchronized(this) {
- if (VDBG) Log.d(TAG, "bindListen(), SocketState: " + mSocketState + ", mPfd: " +
+ if (DBG) Log.d(TAG, "bindListen(), SocketState: " + mSocketState + ", mPfd: " +
mPfd);
if(mSocketState != SocketState.INIT) return EBADFD;
if(mPfd == null) return -1;
FileDescriptor fd = mPfd.getFileDescriptor();
- if (VDBG) Log.d(TAG, "bindListen(), new LocalSocket ");
+ if (DBG) Log.d(TAG, "bindListen(), new LocalSocket ");
mSocket = new LocalSocket(fd);
- if (VDBG) Log.d(TAG, "bindListen(), new LocalSocket.getInputStream() ");
+ if (DBG) Log.d(TAG, "bindListen(), new LocalSocket.getInputStream() ");
mSocketIS = mSocket.getInputStream();
mSocketOS = mSocket.getOutputStream();
}
- if (VDBG) Log.d(TAG, "bindListen(), readInt mSocketIS: " + mSocketIS);
+ if (DBG) Log.d(TAG, "bindListen(), readInt mSocketIS: " + mSocketIS);
int channel = readInt(mSocketIS);
synchronized(this) {
if(mSocketState == SocketState.INIT)
mSocketState = SocketState.LISTENING;
}
- if (VDBG) Log.d(TAG, "channel: " + channel);
+ if (DBG) Log.d(TAG, "channel: " + channel);
if (mPort == -1) {
mPort = channel;
} // else ASSERT(mPort == channel)
@@ -439,7 +439,7 @@ public final class BluetoothSocket implements Closeable {
@Override
public void close() throws IOException {
- if (VDBG) Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + mSocketState);
+ if (DBG) Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + mSocketState);
if(mSocketState == SocketState.CLOSED)
return;
else
@@ -449,10 +449,10 @@ public final class BluetoothSocket implements Closeable {
if(mSocketState == SocketState.CLOSED)
return;
mSocketState = SocketState.CLOSED;
- if (VDBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS +
+ if (DBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS +
", mSocketOS: " + mSocketOS + "mSocket: " + mSocket);
if(mSocket != null) {
- if (VDBG) Log.d(TAG, "Closing mSocket: " + mSocket);
+ if (DBG) Log.d(TAG, "Closing mSocket: " + mSocket);
mSocket.shutdownInput();
mSocket.shutdownOutput();
mSocket.close();
diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
index a0b603e..f0c8299 100644
--- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
+++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
@@ -20,7 +20,7 @@ import android.net.BaseNetworkStateTracker;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.DhcpResults;
-import android.net.LinkCapabilities;
+import android.net.NetworkCapabilities;
import android.net.LinkProperties;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
@@ -75,7 +75,7 @@ public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker {
private BluetoothTetheringDataTracker() {
mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_BLUETOOTH, 0, NETWORKTYPE, "");
mLinkProperties = new LinkProperties();
- mLinkCapabilities = new LinkCapabilities();
+ mNetworkCapabilities = new NetworkCapabilities();
mNetworkInfo.setIsAvailable(false);
setTeardownRequested(false);
@@ -242,16 +242,6 @@ public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker {
}
}
- /**
- * A capability is an Integer/String pair, the capabilities
- * are defined in the class LinkSocket#Key.
- *
- * @return a copy of this connections capabilities, may be empty but never null.
- */
- public LinkCapabilities getLinkCapabilities() {
- return new LinkCapabilities(mLinkCapabilities);
- }
-
/**
* Fetch default gateway address for the network
*/
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index ab53fb0..1e22eb3 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -273,11 +273,29 @@ public final class BluetoothUuid {
* @param parcelUuid
* @return true if the parcelUuid can be converted to 16 bit uuid, false otherwise.
*/
- public static boolean isShortUuid(ParcelUuid parcelUuid) {
+ public static boolean is16BitUuid(ParcelUuid parcelUuid) {
UUID uuid = parcelUuid.getUuid();
if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) {
return false;
}
return ((uuid.getMostSignificantBits() & 0xFFFF0000FFFFFFFFL) == 0x1000L);
}
+
+
+ /**
+ * Check whether the given parcelUuid can be converted to 32 bit bluetooth uuid.
+ *
+ * @param parcelUuid
+ * @return true if the parcelUuid can be converted to 32 bit uuid, false otherwise.
+ */
+ public static boolean is32BitUuid(ParcelUuid parcelUuid) {
+ UUID uuid = parcelUuid.getUuid();
+ if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) {
+ return false;
+ }
+ if (is16BitUuid(parcelUuid)) {
+ return false;
+ }
+ return ((uuid.getMostSignificantBits() & 0xFFFFFFFFL) == 0x1000L);
+ }
}
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 49b156d..00a0750 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -17,6 +17,10 @@
package android.bluetooth;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.AdvertisementData;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanSettings;
import android.os.ParcelUuid;
import android.bluetooth.IBluetoothGattCallback;
@@ -31,8 +35,16 @@ interface IBluetoothGatt {
void startScan(in int appIf, in boolean isServer);
void startScanWithUuids(in int appIf, in boolean isServer, in ParcelUuid[] ids);
+ void startScanWithUuidsScanParam(in int appIf, in boolean isServer,
+ in ParcelUuid[] ids, int scanWindow, int scanInterval);
+ void startScanWithFilters(in int appIf, in boolean isServer,
+ in ScanSettings settings, in List<ScanFilter> filters);
void stopScan(in int appIf, in boolean isServer);
-
+ void startMultiAdvertising(in int appIf,
+ in AdvertisementData advertiseData,
+ in AdvertisementData scanResponse,
+ in AdvertiseSettings settings);
+ void stopMultiAdvertising(in int appIf);
void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback);
void unregisterClient(in int clientIf);
void clientConnect(in int clientIf, in String address, in boolean isDirect, in int transport);
diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
index a78c29b..bf9e0a7 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -64,5 +64,6 @@ interface IBluetoothGattCallback {
in byte[] value);
void onReadRemoteRssi(in String address, in int rssi, in int status);
oneway void onAdvertiseStateChange(in int advertiseState, in int status);
+ oneway void onMultiAdvertiseCallback(in int status);
void onConfigureMTU(in String address, in int mtu, in int status);
}
diff --git a/core/java/android/bluetooth/le/AdvertiseCallback.java b/core/java/android/bluetooth/le/AdvertiseCallback.java
new file mode 100644
index 0000000..f1334c2
--- /dev/null
+++ b/core/java/android/bluetooth/le/AdvertiseCallback.java
@@ -0,0 +1,68 @@
+/*
+ * 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.bluetooth.le;
+
+/**
+ * Callback of Bluetooth LE advertising, which is used to deliver advertising operation status.
+ */
+public abstract class AdvertiseCallback {
+
+ /**
+ * The operation is success.
+ *
+ * @hide
+ */
+ public static final int SUCCESS = 0;
+ /**
+ * Fails to start advertising as the advertisement data contains services that are not added to
+ * the local bluetooth GATT server.
+ */
+ public static final int ADVERTISE_FAILED_SERVICE_UNKNOWN = 1;
+ /**
+ * Fails to start advertising as system runs out of quota for advertisers.
+ */
+ public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2;
+
+ /**
+ * Fails to start advertising as the advertising is already started.
+ */
+ public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3;
+ /**
+ * Fails to stop advertising as the advertising is not started.
+ */
+ public static final int ADVERTISE_FAILED_NOT_STARTED = 4;
+
+ /**
+ * Operation fails due to bluetooth controller failure.
+ */
+ public static final int ADVERTISE_FAILED_CONTROLLER_FAILURE = 5;
+
+ /**
+ * Callback when advertising operation succeeds.
+ *
+ * @param settingsInEffect The actual settings used for advertising, which may be different from
+ * what the app asks.
+ */
+ public abstract void onSuccess(AdvertiseSettings settingsInEffect);
+
+ /**
+ * Callback when advertising operation fails.
+ *
+ * @param errorCode Error code for failures.
+ */
+ public abstract void onFailure(int errorCode);
+}
diff --git a/core/java/android/bluetooth/le/AdvertiseSettings.aidl b/core/java/android/bluetooth/le/AdvertiseSettings.aidl
new file mode 100644
index 0000000..9f47d74
--- /dev/null
+++ b/core/java/android/bluetooth/le/AdvertiseSettings.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.bluetooth.le;
+
+parcelable AdvertiseSettings; \ No newline at end of file
diff --git a/core/java/android/bluetooth/le/AdvertiseSettings.java b/core/java/android/bluetooth/le/AdvertiseSettings.java
new file mode 100644
index 0000000..87d0346
--- /dev/null
+++ b/core/java/android/bluetooth/le/AdvertiseSettings.java
@@ -0,0 +1,218 @@
+/*
+ * 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.bluetooth.le;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The {@link AdvertiseSettings} provide a way to adjust advertising preferences for each
+ * individual advertisement. Use {@link AdvertiseSettings.Builder} to create an instance.
+ */
+public final class AdvertiseSettings implements Parcelable {
+ /**
+ * Perform Bluetooth LE advertising in low power mode. This is the default and preferred
+ * advertising mode as it consumes the least power.
+ */
+ public static final int ADVERTISE_MODE_LOW_POWER = 0;
+ /**
+ * Perform Bluetooth LE advertising in balanced power mode. This is balanced between advertising
+ * frequency and power consumption.
+ */
+ public static final int ADVERTISE_MODE_BALANCED = 1;
+ /**
+ * Perform Bluetooth LE advertising in low latency, high power mode. This has the highest power
+ * consumption and should not be used for background continuous advertising.
+ */
+ public static final int ADVERTISE_MODE_LOW_LATENCY = 2;
+
+ /**
+ * Advertise using the lowest transmission(tx) power level. An app can use low transmission
+ * power to restrict the visibility range of its advertising packet.
+ */
+ public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0;
+ /**
+ * Advertise using low tx power level.
+ */
+ public static final int ADVERTISE_TX_POWER_LOW = 1;
+ /**
+ * Advertise using medium tx power level.
+ */
+ public static final int ADVERTISE_TX_POWER_MEDIUM = 2;
+ /**
+ * Advertise using high tx power level. This is corresponding to largest visibility range of the
+ * advertising packet.
+ */
+ public static final int ADVERTISE_TX_POWER_HIGH = 3;
+
+ /**
+ * Non-connectable undirected advertising event, as defined in Bluetooth Specification V4.1
+ * vol6, part B, section 4.4.2 - Advertising state.
+ */
+ public static final int ADVERTISE_TYPE_NON_CONNECTABLE = 0;
+ /**
+ * Scannable undirected advertise type, as defined in same spec mentioned above. This event type
+ * allows a scanner to send a scan request asking additional information about the advertiser.
+ */
+ public static final int ADVERTISE_TYPE_SCANNABLE = 1;
+ /**
+ * Connectable undirected advertising type, as defined in same spec mentioned above. This event
+ * type allows a scanner to send scan request asking additional information about the
+ * advertiser. It also allows an initiator to send a connect request for connection.
+ */
+ public static final int ADVERTISE_TYPE_CONNECTABLE = 2;
+
+ private final int mAdvertiseMode;
+ private final int mAdvertiseTxPowerLevel;
+ private final int mAdvertiseEventType;
+
+ private AdvertiseSettings(int advertiseMode, int advertiseTxPowerLevel,
+ int advertiseEventType) {
+ mAdvertiseMode = advertiseMode;
+ mAdvertiseTxPowerLevel = advertiseTxPowerLevel;
+ mAdvertiseEventType = advertiseEventType;
+ }
+
+ private AdvertiseSettings(Parcel in) {
+ mAdvertiseMode = in.readInt();
+ mAdvertiseTxPowerLevel = in.readInt();
+ mAdvertiseEventType = in.readInt();
+ }
+
+ /**
+ * Returns the advertise mode.
+ */
+ public int getMode() {
+ return mAdvertiseMode;
+ }
+
+ /**
+ * Returns the tx power level for advertising.
+ */
+ public int getTxPowerLevel() {
+ return mAdvertiseTxPowerLevel;
+ }
+
+ /**
+ * Returns the advertise event type.
+ */
+ public int getType() {
+ return mAdvertiseEventType;
+ }
+
+ @Override
+ public String toString() {
+ return "Settings [mAdvertiseMode=" + mAdvertiseMode + ", mAdvertiseTxPowerLevel="
+ + mAdvertiseTxPowerLevel + ", mAdvertiseEventType=" + mAdvertiseEventType + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mAdvertiseMode);
+ dest.writeInt(mAdvertiseTxPowerLevel);
+ dest.writeInt(mAdvertiseEventType);
+ }
+
+ public static final Parcelable.Creator<AdvertiseSettings> CREATOR =
+ new Creator<AdvertiseSettings>() {
+ @Override
+ public AdvertiseSettings[] newArray(int size) {
+ return new AdvertiseSettings[size];
+ }
+
+ @Override
+ public AdvertiseSettings createFromParcel(Parcel in) {
+ return new AdvertiseSettings(in);
+ }
+ };
+
+ /**
+ * Builder class for {@link AdvertiseSettings}.
+ */
+ public static final class Builder {
+ private int mMode = ADVERTISE_MODE_LOW_POWER;
+ private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM;
+ private int mType = ADVERTISE_TYPE_NON_CONNECTABLE;
+
+ /**
+ * Set advertise mode to control the advertising power and latency.
+ *
+ * @param advertiseMode Bluetooth LE Advertising mode, can only be one of
+ * {@link AdvertiseSettings#ADVERTISE_MODE_LOW_POWER},
+ * {@link AdvertiseSettings#ADVERTISE_MODE_BALANCED}, or
+ * {@link AdvertiseSettings#ADVERTISE_MODE_LOW_LATENCY}.
+ * @throws IllegalArgumentException If the advertiseMode is invalid.
+ */
+ public Builder setAdvertiseMode(int advertiseMode) {
+ if (advertiseMode < ADVERTISE_MODE_LOW_POWER
+ || advertiseMode > ADVERTISE_MODE_LOW_LATENCY) {
+ throw new IllegalArgumentException("unknown mode " + advertiseMode);
+ }
+ mMode = advertiseMode;
+ return this;
+ }
+
+ /**
+ * Set advertise tx power level to control the transmission power level for the advertising.
+ *
+ * @param txPowerLevel Transmission power of Bluetooth LE Advertising, can only be one of
+ * {@link AdvertiseSettings#ADVERTISE_TX_POWER_ULTRA_LOW},
+ * {@link AdvertiseSettings#ADVERTISE_TX_POWER_LOW},
+ * {@link AdvertiseSettings#ADVERTISE_TX_POWER_MEDIUM} or
+ * {@link AdvertiseSettings#ADVERTISE_TX_POWER_HIGH}.
+ * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid.
+ */
+ public Builder setTxPowerLevel(int txPowerLevel) {
+ if (txPowerLevel < ADVERTISE_TX_POWER_ULTRA_LOW
+ || txPowerLevel > ADVERTISE_TX_POWER_HIGH) {
+ throw new IllegalArgumentException("unknown tx power level " + txPowerLevel);
+ }
+ mTxPowerLevel = txPowerLevel;
+ return this;
+ }
+
+ /**
+ * Set advertise type to control the event type of advertising.
+ *
+ * @param type Bluetooth LE Advertising type, can be either
+ * {@link AdvertiseSettings#ADVERTISE_TYPE_NON_CONNECTABLE},
+ * {@link AdvertiseSettings#ADVERTISE_TYPE_SCANNABLE} or
+ * {@link AdvertiseSettings#ADVERTISE_TYPE_CONNECTABLE}.
+ * @throws IllegalArgumentException If the {@code type} is invalid.
+ */
+ public Builder setType(int type) {
+ if (type < ADVERTISE_TYPE_NON_CONNECTABLE
+ || type > ADVERTISE_TYPE_CONNECTABLE) {
+ throw new IllegalArgumentException("unknown advertise type " + type);
+ }
+ mType = type;
+ return this;
+ }
+
+ /**
+ * Build the {@link AdvertiseSettings} object.
+ */
+ public AdvertiseSettings build() {
+ return new AdvertiseSettings(mMode, mTxPowerLevel, mType);
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/core/java/android/bluetooth/le/AdvertisementData.aidl
new file mode 100644
index 0000000..3da1321
--- /dev/null
+++ b/core/java/android/bluetooth/le/AdvertisementData.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.bluetooth.le;
+
+parcelable AdvertisementData; \ No newline at end of file
diff --git a/core/java/android/bluetooth/le/AdvertisementData.java b/core/java/android/bluetooth/le/AdvertisementData.java
new file mode 100644
index 0000000..c587204
--- /dev/null
+++ b/core/java/android/bluetooth/le/AdvertisementData.java
@@ -0,0 +1,344 @@
+/*
+ * 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.bluetooth.le;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothUuid;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Advertisement data packet for Bluetooth LE advertising. This represents the data to be
+ * broadcasted in Bluetooth LE advertising as well as the scan response for active scan.
+ * <p>
+ * Use {@link AdvertisementData.Builder} to create an instance of {@link AdvertisementData} to be
+ * advertised.
+ *
+ * @see BluetoothLeAdvertiser
+ * @see ScanRecord
+ */
+public final class AdvertisementData implements Parcelable {
+
+ @Nullable
+ private final List<ParcelUuid> mServiceUuids;
+
+ private final int mManufacturerId;
+ @Nullable
+ private final byte[] mManufacturerSpecificData;
+
+ @Nullable
+ private final ParcelUuid mServiceDataUuid;
+ @Nullable
+ private final byte[] mServiceData;
+
+ private boolean mIncludeTxPowerLevel;
+
+ private AdvertisementData(List<ParcelUuid> serviceUuids,
+ ParcelUuid serviceDataUuid, byte[] serviceData,
+ int manufacturerId,
+ byte[] manufacturerSpecificData, boolean includeTxPowerLevel) {
+ mServiceUuids = serviceUuids;
+ mManufacturerId = manufacturerId;
+ mManufacturerSpecificData = manufacturerSpecificData;
+ mServiceDataUuid = serviceDataUuid;
+ mServiceData = serviceData;
+ mIncludeTxPowerLevel = includeTxPowerLevel;
+ }
+
+ /**
+ * Returns a list of service uuids within the advertisement that are used to identify the
+ * bluetooth GATT services.
+ */
+ public List<ParcelUuid> getServiceUuids() {
+ return mServiceUuids;
+ }
+
+ /**
+ * Returns the manufacturer identifier, which is a non-negative number assigned by Bluetooth
+ * SIG.
+ */
+ public int getManufacturerId() {
+ return mManufacturerId;
+ }
+
+ /**
+ * Returns the manufacturer specific data which is the content of manufacturer specific data
+ * field. The first 2 bytes of the data contain the company id.
+ */
+ public byte[] getManufacturerSpecificData() {
+ return mManufacturerSpecificData;
+ }
+
+ /**
+ * Returns a 16 bit uuid of the service that the service data is associated with.
+ */
+ public ParcelUuid getServiceDataUuid() {
+ return mServiceDataUuid;
+ }
+
+ /**
+ * Returns service data. The first two bytes should be a 16 bit service uuid associated with the
+ * service data.
+ */
+ public byte[] getServiceData() {
+ return mServiceData;
+ }
+
+ /**
+ * Whether the transmission power level will be included in the advertisement packet.
+ */
+ public boolean getIncludeTxPowerLevel() {
+ return mIncludeTxPowerLevel;
+ }
+
+ @Override
+ public String toString() {
+ return "AdvertisementData [mServiceUuids=" + mServiceUuids + ", mManufacturerId="
+ + mManufacturerId + ", mManufacturerSpecificData="
+ + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid="
+ + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData)
+ + ", mIncludeTxPowerLevel=" + mIncludeTxPowerLevel + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mServiceUuids == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(mServiceUuids.size());
+ dest.writeList(mServiceUuids);
+ }
+
+ dest.writeInt(mManufacturerId);
+ if (mManufacturerSpecificData == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(mManufacturerSpecificData.length);
+ dest.writeByteArray(mManufacturerSpecificData);
+ }
+
+ if (mServiceDataUuid == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ dest.writeParcelable(mServiceDataUuid, flags);
+ if (mServiceData == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(mServiceData.length);
+ dest.writeByteArray(mServiceData);
+ }
+ }
+ dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0));
+ }
+
+ public static final Parcelable.Creator<AdvertisementData> CREATOR =
+ new Creator<AdvertisementData>() {
+ @Override
+ public AdvertisementData[] newArray(int size) {
+ return new AdvertisementData[size];
+ }
+
+ @Override
+ public AdvertisementData createFromParcel(Parcel in) {
+ Builder builder = new Builder();
+ if (in.readInt() > 0) {
+ List<ParcelUuid> uuids = new ArrayList<ParcelUuid>();
+ in.readList(uuids, ParcelUuid.class.getClassLoader());
+ builder.setServiceUuids(uuids);
+ }
+ int manufacturerId = in.readInt();
+ int manufacturerDataLength = in.readInt();
+ if (manufacturerDataLength > 0) {
+ byte[] manufacturerData = new byte[manufacturerDataLength];
+ in.readByteArray(manufacturerData);
+ builder.setManufacturerData(manufacturerId, manufacturerData);
+ }
+ if (in.readInt() == 1) {
+ ParcelUuid serviceDataUuid = in.readParcelable(
+ ParcelUuid.class.getClassLoader());
+ int serviceDataLength = in.readInt();
+ if (serviceDataLength > 0) {
+ byte[] serviceData = new byte[serviceDataLength];
+ in.readByteArray(serviceData);
+ builder.setServiceData(serviceDataUuid, serviceData);
+ }
+ }
+ builder.setIncludeTxPowerLevel(in.readByte() == 1);
+ return builder.build();
+ }
+ };
+
+ /**
+ * Builder for {@link AdvertisementData}.
+ */
+ public static final class Builder {
+ private static final int MAX_ADVERTISING_DATA_BYTES = 31;
+ // Each fields need one byte for field length and another byte for field type.
+ private static final int OVERHEAD_BYTES_PER_FIELD = 2;
+ // Flags field will be set by system.
+ private static final int FLAGS_FIELD_BYTES = 3;
+
+ @Nullable
+ private List<ParcelUuid> mServiceUuids;
+ private boolean mIncludeTxPowerLevel;
+ private int mManufacturerId;
+ @Nullable
+ private byte[] mManufacturerSpecificData;
+ @Nullable
+ private ParcelUuid mServiceDataUuid;
+ @Nullable
+ private byte[] mServiceData;
+
+ /**
+ * Set the service uuids. Note the corresponding bluetooth Gatt services need to be already
+ * added on the device before start BLE advertising.
+ *
+ * @param serviceUuids Service uuids to be advertised, could be 16-bit, 32-bit or 128-bit
+ * uuids.
+ * @throws IllegalArgumentException If the {@code serviceUuids} are null.
+ */
+ public Builder setServiceUuids(List<ParcelUuid> serviceUuids) {
+ if (serviceUuids == null) {
+ throw new IllegalArgumentException("serivceUuids are null");
+ }
+ mServiceUuids = serviceUuids;
+ return this;
+ }
+
+ /**
+ * Add service data to advertisement.
+ *
+ * @param serviceDataUuid A 16 bit uuid of the service data
+ * @param serviceData Service data - the first two bytes of the service data are the service
+ * data uuid.
+ * @throws IllegalArgumentException If the {@code serviceDataUuid} or {@code serviceData} is
+ * empty.
+ */
+ public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
+ if (serviceDataUuid == null || serviceData == null) {
+ throw new IllegalArgumentException(
+ "serviceDataUuid or serviceDataUuid is null");
+ }
+ mServiceDataUuid = serviceDataUuid;
+ mServiceData = serviceData;
+ return this;
+ }
+
+ /**
+ * Set manufacturer id and data. See <a
+ * href="https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers">assigned
+ * manufacturer identifies</a> for the existing company identifiers.
+ *
+ * @param manufacturerId Manufacturer id assigned by Bluetooth SIG.
+ * @param manufacturerSpecificData Manufacturer specific data - the first two bytes of the
+ * manufacturer specific data are the manufacturer id.
+ * @throws IllegalArgumentException If the {@code manufacturerId} is negative or
+ * {@code manufacturerSpecificData} is null.
+ */
+ public Builder setManufacturerData(int manufacturerId, byte[] manufacturerSpecificData) {
+ if (manufacturerId < 0) {
+ throw new IllegalArgumentException(
+ "invalid manufacturerId - " + manufacturerId);
+ }
+ if (manufacturerSpecificData == null) {
+ throw new IllegalArgumentException("manufacturerSpecificData is null");
+ }
+ mManufacturerId = manufacturerId;
+ mManufacturerSpecificData = manufacturerSpecificData;
+ return this;
+ }
+
+ /**
+ * Whether the transmission power level should be included in the advertising packet.
+ */
+ public Builder setIncludeTxPowerLevel(boolean includeTxPowerLevel) {
+ mIncludeTxPowerLevel = includeTxPowerLevel;
+ return this;
+ }
+
+ /**
+ * Build the {@link AdvertisementData}.
+ *
+ * @throws IllegalArgumentException If the data size is larger than 31 bytes.
+ */
+ public AdvertisementData build() {
+ if (totalBytes() > MAX_ADVERTISING_DATA_BYTES) {
+ throw new IllegalArgumentException(
+ "advertisement data size is larger than 31 bytes");
+ }
+ return new AdvertisementData(mServiceUuids,
+ mServiceDataUuid,
+ mServiceData, mManufacturerId, mManufacturerSpecificData,
+ mIncludeTxPowerLevel);
+ }
+
+ // Compute the size of the advertisement data.
+ private int totalBytes() {
+ int size = FLAGS_FIELD_BYTES; // flags field is always set.
+ if (mServiceUuids != null) {
+ int num16BitUuids = 0;
+ int num32BitUuids = 0;
+ int num128BitUuids = 0;
+ for (ParcelUuid uuid : mServiceUuids) {
+ if (BluetoothUuid.is16BitUuid(uuid)) {
+ ++num16BitUuids;
+ } else if (BluetoothUuid.is32BitUuid(uuid)) {
+ ++num32BitUuids;
+ } else {
+ ++num128BitUuids;
+ }
+ }
+ // 16 bit service uuids are grouped into one field when doing advertising.
+ if (num16BitUuids != 0) {
+ size += OVERHEAD_BYTES_PER_FIELD +
+ num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT;
+ }
+ // 32 bit service uuids are grouped into one field when doing advertising.
+ if (num32BitUuids != 0) {
+ size += OVERHEAD_BYTES_PER_FIELD +
+ num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT;
+ }
+ // 128 bit service uuids are grouped into one field when doing advertising.
+ if (num128BitUuids != 0) {
+ size += OVERHEAD_BYTES_PER_FIELD +
+ num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT;
+ }
+ }
+ if (mServiceData != null) {
+ size += OVERHEAD_BYTES_PER_FIELD + mServiceData.length;
+ }
+ if (mManufacturerSpecificData != null) {
+ size += OVERHEAD_BYTES_PER_FIELD + mManufacturerSpecificData.length;
+ }
+ if (mIncludeTxPowerLevel) {
+ size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte.
+ }
+ return size;
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
new file mode 100644
index 0000000..ed43407
--- /dev/null
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -0,0 +1,368 @@
+/*
+ * 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.bluetooth.le;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.IBluetoothGatt;
+import android.bluetooth.IBluetoothGattCallback;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * This class provides a way to perform Bluetooth LE advertise operations, such as start and stop
+ * advertising. An advertiser can broadcast up to 31 bytes of advertisement data represented by
+ * {@link AdvertisementData}.
+ * <p>
+ * To get an instance of {@link BluetoothLeAdvertiser}, call the
+ * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method.
+ * <p>
+ * Note most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ * permission.
+ *
+ * @see AdvertisementData
+ */
+public final class BluetoothLeAdvertiser {
+
+ private static final String TAG = "BluetoothLeAdvertiser";
+
+ private final IBluetoothGatt mBluetoothGatt;
+ private final Handler mHandler;
+ private final Map<AdvertiseCallback, AdvertiseCallbackWrapper>
+ mLeAdvertisers = new HashMap<AdvertiseCallback, AdvertiseCallbackWrapper>();
+
+ /**
+ * Use BluetoothAdapter.getLeAdvertiser() instead.
+ *
+ * @param bluetoothGatt
+ * @hide
+ */
+ public BluetoothLeAdvertiser(IBluetoothGatt bluetoothGatt) {
+ mBluetoothGatt = bluetoothGatt;
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ /**
+ * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the
+ * operation succeeds. Returns immediately, the operation status are delivered through
+ * {@code callback}.
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param settings Settings for Bluetooth LE advertising.
+ * @param advertiseData Advertisement data to be broadcasted.
+ * @param callback Callback for advertising status.
+ */
+ public void startAdvertising(AdvertiseSettings settings,
+ AdvertisementData advertiseData, final AdvertiseCallback callback) {
+ startAdvertising(settings, advertiseData, null, callback);
+ }
+
+ /**
+ * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the
+ * operation succeeds. The {@code scanResponse} would be returned when the scanning device sends
+ * active scan request. Method returns immediately, the operation status are delivered through
+ * {@code callback}.
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ *
+ * @param settings Settings for Bluetooth LE advertising.
+ * @param advertiseData Advertisement data to be advertised in advertisement packet.
+ * @param scanResponse Scan response associated with the advertisement data.
+ * @param callback Callback for advertising status.
+ */
+ public void startAdvertising(AdvertiseSettings settings,
+ AdvertisementData advertiseData, AdvertisementData scanResponse,
+ final AdvertiseCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+ if (mLeAdvertisers.containsKey(callback)) {
+ postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
+ return;
+ }
+ AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData,
+ scanResponse, settings, mBluetoothGatt);
+ UUID uuid = UUID.randomUUID();
+ try {
+ mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper);
+ if (wrapper.advertiseStarted()) {
+ mLeAdvertisers.put(callback, wrapper);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to stop advertising", e);
+ }
+ }
+
+ /**
+ * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
+ * {@link BluetoothLeAdvertiser#startAdvertising}.
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param callback {@link AdvertiseCallback} for delivering stopping advertising status.
+ */
+ public void stopAdvertising(final AdvertiseCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+ AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback);
+ if (wrapper == null) {
+ postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_NOT_STARTED);
+ return;
+ }
+ try {
+ mBluetoothGatt.stopMultiAdvertising(wrapper.mLeHandle);
+ if (wrapper.advertiseStopped()) {
+ mLeAdvertisers.remove(callback);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to stop advertising", e);
+ }
+ }
+
+ /**
+ * Bluetooth GATT interface callbacks for advertising.
+ */
+ private static class AdvertiseCallbackWrapper extends IBluetoothGattCallback.Stub {
+ private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;
+ private final AdvertiseCallback mAdvertiseCallback;
+ private final AdvertisementData mAdvertisement;
+ private final AdvertisementData mScanResponse;
+ private final AdvertiseSettings mSettings;
+ private final IBluetoothGatt mBluetoothGatt;
+
+ // mLeHandle 0: not registered
+ // -1: scan stopped
+ // >0: registered and scan started
+ private int mLeHandle;
+ private boolean isAdvertising = false;
+
+ public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
+ AdvertisementData advertiseData, AdvertisementData scanResponse,
+ AdvertiseSettings settings,
+ IBluetoothGatt bluetoothGatt) {
+ mAdvertiseCallback = advertiseCallback;
+ mAdvertisement = advertiseData;
+ mScanResponse = scanResponse;
+ mSettings = settings;
+ mBluetoothGatt = bluetoothGatt;
+ mLeHandle = 0;
+ }
+
+ public boolean advertiseStarted() {
+ boolean started = false;
+ synchronized (this) {
+ if (mLeHandle == -1) {
+ return false;
+ }
+ try {
+ wait(LE_CALLBACK_TIMEOUT_MILLIS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Callback reg wait interrupted: ", e);
+ }
+ started = (mLeHandle > 0 && isAdvertising);
+ }
+ return started;
+ }
+
+ public boolean advertiseStopped() {
+ synchronized (this) {
+ try {
+ wait(LE_CALLBACK_TIMEOUT_MILLIS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Callback reg wait interrupted: " + e);
+ }
+ return !isAdvertising;
+ }
+ }
+
+ /**
+ * Application interface registered - app is ready to go
+ */
+ @Override
+ public void onClientRegistered(int status, int clientIf) {
+ Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf);
+ synchronized (this) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ mLeHandle = clientIf;
+ try {
+ mBluetoothGatt.startMultiAdvertising(mLeHandle, mAdvertisement,
+ mScanResponse, mSettings);
+ } catch (RemoteException e) {
+ Log.e(TAG, "fail to start le advertise: " + e);
+ mLeHandle = -1;
+ notifyAll();
+ } catch (Exception e) {
+ Log.e(TAG, "fail to start advertise: " + e.getStackTrace());
+ }
+ } else {
+ // registration failed
+ mLeHandle = -1;
+ notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public void onClientConnectionState(int status, int clientIf,
+ boolean connected, String address) {
+ // no op
+ }
+
+ @Override
+ public void onScanResult(String address, int rssi, byte[] advData) {
+ // no op
+ }
+
+ @Override
+ public void onGetService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid) {
+ // no op
+ }
+
+ @Override
+ public void onGetIncludedService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int inclSrvcType, int inclSrvcInstId,
+ ParcelUuid inclSrvcUuid) {
+ // no op
+ }
+
+ @Override
+ public void onGetCharacteristic(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int charProps) {
+ // no op
+ }
+
+ @Override
+ public void onGetDescriptor(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descUuid) {
+ // no op
+ }
+
+ @Override
+ public void onSearchComplete(String address, int status) {
+ // no op
+ }
+
+ @Override
+ public void onCharacteristicRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid, byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onCharacteristicWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid) {
+ // no op
+ }
+
+ @Override
+ public void onNotify(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onDescriptorRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descrUuid, byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onDescriptorWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descrUuid) {
+ // no op
+ }
+
+ @Override
+ public void onExecuteWrite(String address, int status) {
+ // no op
+ }
+
+ @Override
+ public void onReadRemoteRssi(String address, int rssi, int status) {
+ // no op
+ }
+
+ @Override
+ public void onAdvertiseStateChange(int advertiseState, int status) {
+ // no op
+ }
+
+ @Override
+ public void onMultiAdvertiseCallback(int status) {
+ synchronized (this) {
+ if (status == 0) {
+ isAdvertising = !isAdvertising;
+ if (!isAdvertising) {
+ try {
+ mBluetoothGatt.unregisterClient(mLeHandle);
+ mLeHandle = -1;
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception when unregistering", e);
+ }
+ }
+ mAdvertiseCallback.onSuccess(null);
+ } else {
+ mAdvertiseCallback.onFailure(status);
+ }
+ notifyAll();
+ }
+
+ }
+
+ /**
+ * Callback reporting LE ATT MTU.
+ *
+ * @hide
+ */
+ @Override
+ public void onConfigureMTU(String address, int mtu, int status) {
+ // no op
+ }
+ }
+
+ private void postCallbackFailure(final AdvertiseCallback callback, final int error) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onFailure(error);
+ }
+ });
+ }
+}
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
new file mode 100644
index 0000000..4c6346c
--- /dev/null
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -0,0 +1,371 @@
+/*
+ * 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.bluetooth.le;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.IBluetoothGatt;
+import android.bluetooth.IBluetoothGattCallback;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * This class provides methods to perform scan related operations for Bluetooth LE devices. An
+ * application can scan for a particular type of BLE devices using {@link ScanFilter}. It can also
+ * request different types of callbacks for delivering the result.
+ * <p>
+ * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of
+ * {@link BluetoothLeScanner}.
+ * <p>
+ * Note most of the scan methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ * permission.
+ *
+ * @see ScanFilter
+ */
+public final class BluetoothLeScanner {
+
+ private static final String TAG = "BluetoothLeScanner";
+ private static final boolean DBG = true;
+
+ private final IBluetoothGatt mBluetoothGatt;
+ private final Handler mHandler;
+ private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients;
+
+ /**
+ * @hide
+ */
+ public BluetoothLeScanner(IBluetoothGatt bluetoothGatt) {
+ mBluetoothGatt = bluetoothGatt;
+ mHandler = new Handler(Looper.getMainLooper());
+ mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>();
+ }
+
+ /**
+ * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}.
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param filters {@link ScanFilter}s for finding exact BLE devices.
+ * @param settings Settings for ble scan.
+ * @param callback Callback when scan results are delivered.
+ * @throws IllegalArgumentException If {@code settings} or {@code callback} is null.
+ */
+ public void startScan(List<ScanFilter> filters, ScanSettings settings,
+ final ScanCallback callback) {
+ if (settings == null || callback == null) {
+ throw new IllegalArgumentException("settings or callback is null");
+ }
+ synchronized (mLeScanClients) {
+ if (mLeScanClients.containsKey(callback)) {
+ postCallbackError(callback, ScanCallback.SCAN_FAILED_ALREADY_STARTED);
+ return;
+ }
+ BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(mBluetoothGatt, filters,
+ settings, callback);
+ try {
+ UUID uuid = UUID.randomUUID();
+ mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper);
+ if (wrapper.scanStarted()) {
+ mLeScanClients.put(callback, wrapper);
+ } else {
+ postCallbackError(callback,
+ ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED);
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "GATT service exception when starting scan", e);
+ postCallbackError(callback, ScanCallback.SCAN_FAILED_GATT_SERVICE_FAILURE);
+ }
+ }
+ }
+
+ /**
+ * Stops an ongoing Bluetooth LE scan.
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param callback
+ */
+ public void stopScan(ScanCallback callback) {
+ synchronized (mLeScanClients) {
+ BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback);
+ if (wrapper == null) {
+ return;
+ }
+ wrapper.stopLeScan();
+ }
+ }
+
+ /**
+ * Returns available storage size for batch scan results. It's recommended not to use batch scan
+ * if available storage size is small (less than 1k bytes, for instance).
+ *
+ * @hide TODO: unhide when batching is supported in stack.
+ */
+ public int getAvailableBatchStorageSizeBytes() {
+ throw new UnsupportedOperationException("not impelemented");
+ }
+
+ /**
+ * Poll scan results from bluetooth controller. This will return Bluetooth LE scan results
+ * batched on bluetooth controller.
+ *
+ * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one
+ * used to start scan.
+ * @param flush Whether to flush the batch scan buffer. Note the other batch scan clients will
+ * get batch scan callback if the batch scan buffer is flushed.
+ * @return Batch Scan results.
+ * @hide TODO: unhide when batching is supported in stack.
+ */
+ public List<ScanResult> getBatchScanResults(ScanCallback callback, boolean flush) {
+ throw new UnsupportedOperationException("not impelemented");
+ }
+
+ /**
+ * Bluetooth GATT interface callbacks
+ */
+ private static class BleScanCallbackWrapper extends IBluetoothGattCallback.Stub {
+ private static final int REGISTRATION_CALLBACK_TIMEOUT_SECONDS = 5;
+
+ private final ScanCallback mScanCallback;
+ private final List<ScanFilter> mFilters;
+ private ScanSettings mSettings;
+ private IBluetoothGatt mBluetoothGatt;
+
+ // mLeHandle 0: not registered
+ // -1: scan stopped
+ // > 0: registered and scan started
+ private int mLeHandle;
+
+ public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt,
+ List<ScanFilter> filters, ScanSettings settings,
+ ScanCallback scanCallback) {
+ mBluetoothGatt = bluetoothGatt;
+ mFilters = filters;
+ mSettings = settings;
+ mScanCallback = scanCallback;
+ mLeHandle = 0;
+ }
+
+ public boolean scanStarted() {
+ synchronized (this) {
+ if (mLeHandle == -1) {
+ return false;
+ }
+ try {
+ wait(REGISTRATION_CALLBACK_TIMEOUT_SECONDS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Callback reg wait interrupted: " + e);
+ }
+ }
+ return mLeHandle > 0;
+ }
+
+ public void stopLeScan() {
+ synchronized (this) {
+ if (mLeHandle <= 0) {
+ Log.e(TAG, "Error state, mLeHandle: " + mLeHandle);
+ return;
+ }
+ try {
+ mBluetoothGatt.stopScan(mLeHandle, false);
+ mBluetoothGatt.unregisterClient(mLeHandle);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to stop scan and unregister" + e);
+ }
+ mLeHandle = -1;
+ notifyAll();
+ }
+ }
+
+ /**
+ * Application interface registered - app is ready to go
+ */
+ @Override
+ public void onClientRegistered(int status, int clientIf) {
+ Log.d(TAG, "onClientRegistered() - status=" + status +
+ " clientIf=" + clientIf);
+
+ synchronized (this) {
+ if (mLeHandle == -1) {
+ if (DBG)
+ Log.d(TAG, "onClientRegistered LE scan canceled");
+ }
+
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ mLeHandle = clientIf;
+ try {
+ mBluetoothGatt.startScanWithFilters(mLeHandle, false, mSettings, mFilters);
+ } catch (RemoteException e) {
+ Log.e(TAG, "fail to start le scan: " + e);
+ mLeHandle = -1;
+ }
+ } else {
+ // registration failed
+ mLeHandle = -1;
+ }
+ notifyAll();
+ }
+ }
+
+ @Override
+ public void onClientConnectionState(int status, int clientIf,
+ boolean connected, String address) {
+ // no op
+ }
+
+ /**
+ * Callback reporting an LE scan result.
+ *
+ * @hide
+ */
+ @Override
+ public void onScanResult(String address, int rssi, byte[] advData) {
+ if (DBG)
+ Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" + rssi);
+
+ // Check null in case the scan has been stopped
+ synchronized (this) {
+ if (mLeHandle <= 0)
+ return;
+ }
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+ address);
+ long scanNanos = SystemClock.elapsedRealtimeNanos();
+ ScanResult result = new ScanResult(device, advData, rssi,
+ scanNanos);
+ mScanCallback.onAdvertisementUpdate(result);
+ }
+
+ @Override
+ public void onGetService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid) {
+ // no op
+ }
+
+ @Override
+ public void onGetIncludedService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int inclSrvcType, int inclSrvcInstId,
+ ParcelUuid inclSrvcUuid) {
+ // no op
+ }
+
+ @Override
+ public void onGetCharacteristic(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int charProps) {
+ // no op
+ }
+
+ @Override
+ public void onGetDescriptor(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descUuid) {
+ // no op
+ }
+
+ @Override
+ public void onSearchComplete(String address, int status) {
+ // no op
+ }
+
+ @Override
+ public void onCharacteristicRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid, byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onCharacteristicWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid) {
+ // no op
+ }
+
+ @Override
+ public void onNotify(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onDescriptorRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descrUuid, byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onDescriptorWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descrUuid) {
+ // no op
+ }
+
+ @Override
+ public void onExecuteWrite(String address, int status) {
+ // no op
+ }
+
+ @Override
+ public void onReadRemoteRssi(String address, int rssi, int status) {
+ // no op
+ }
+
+ @Override
+ public void onAdvertiseStateChange(int advertiseState, int status) {
+ // no op
+ }
+
+ @Override
+ public void onMultiAdvertiseCallback(int status) {
+ // no op
+ }
+
+ @Override
+ public void onConfigureMTU(String address, int mtu, int status) {
+ // no op
+ }
+ }
+
+ private void postCallbackError(final ScanCallback callback, final int errorCode) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onScanFailed(errorCode);
+ }
+ });
+ }
+}
diff --git a/core/java/android/bluetooth/le/ScanCallback.java b/core/java/android/bluetooth/le/ScanCallback.java
new file mode 100644
index 0000000..50ebf50
--- /dev/null
+++ b/core/java/android/bluetooth/le/ScanCallback.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.bluetooth.le;
+
+import java.util.List;
+
+/**
+ * Callback of Bluetooth LE scans. The results of the scans will be delivered through the callbacks.
+ */
+public abstract class ScanCallback {
+
+ /**
+ * Fails to start scan as BLE scan with the same settings is already started by the app.
+ */
+ public static final int SCAN_FAILED_ALREADY_STARTED = 1;
+ /**
+ * Fails to start scan as app cannot be registered.
+ */
+ public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2;
+ /**
+ * Fails to start scan due to gatt service failure.
+ */
+ public static final int SCAN_FAILED_GATT_SERVICE_FAILURE = 3;
+ /**
+ * Fails to start scan due to controller failure.
+ */
+ public static final int SCAN_FAILED_CONTROLLER_FAILURE = 4;
+
+ /**
+ * Callback when a BLE advertisement is found.
+ *
+ * @param result A Bluetooth LE scan result.
+ */
+ public abstract void onAdvertisementUpdate(ScanResult result);
+
+ /**
+ * Callback when the BLE advertisement is found for the first time.
+ *
+ * @param result The Bluetooth LE scan result when the onFound event is triggered.
+ * @hide
+ */
+ public abstract void onAdvertisementFound(ScanResult result);
+
+ /**
+ * Callback when the BLE advertisement was lost. Note a device has to be "found" before it's
+ * lost.
+ *
+ * @param result The Bluetooth scan result that was last found.
+ * @hide
+ */
+ public abstract void onAdvertisementLost(ScanResult result);
+
+ /**
+ * Callback when batch results are delivered.
+ *
+ * @param results List of scan results that are previously scanned.
+ * @hide
+ */
+ public abstract void onBatchScanResults(List<ScanResult> results);
+
+ /**
+ * Callback when scan failed.
+ */
+ public abstract void onScanFailed(int errorCode);
+}
diff --git a/core/java/android/bluetooth/le/ScanFilter.aidl b/core/java/android/bluetooth/le/ScanFilter.aidl
new file mode 100644
index 0000000..4cecfe6
--- /dev/null
+++ b/core/java/android/bluetooth/le/ScanFilter.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.bluetooth.le;
+
+parcelable ScanFilter;
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
new file mode 100644
index 0000000..c2e316b
--- /dev/null
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -0,0 +1,588 @@
+/*
+ * 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.bluetooth.le;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * {@link ScanFilter} abstracts different scan filters across Bluetooth Advertisement packet fields.
+ * <p>
+ * Current filtering on the following fields are supported:
+ * <li>Service UUIDs which identify the bluetooth gatt services running on the device.
+ * <li>Name of remote Bluetooth LE device.
+ * <li>Mac address of the remote device.
+ * <li>Rssi which indicates the received power level.
+ * <li>Service data which is the data associated with a service.
+ * <li>Manufacturer specific data which is the data associated with a particular manufacturer.
+ *
+ * @see ScanRecord
+ * @see BluetoothLeScanner
+ */
+public final class ScanFilter implements Parcelable {
+
+ @Nullable
+ private final String mLocalName;
+
+ @Nullable
+ private final String mMacAddress;
+
+ @Nullable
+ private final ParcelUuid mServiceUuid;
+ @Nullable
+ private final ParcelUuid mServiceUuidMask;
+
+ @Nullable
+ private final byte[] mServiceData;
+ @Nullable
+ private final byte[] mServiceDataMask;
+
+ private final int mManufacturerId;
+ @Nullable
+ private final byte[] mManufacturerData;
+ @Nullable
+ private final byte[] mManufacturerDataMask;
+
+ private final int mMinRssi;
+ private final int mMaxRssi;
+
+ private ScanFilter(String name, String macAddress, ParcelUuid uuid,
+ ParcelUuid uuidMask, byte[] serviceData, byte[] serviceDataMask,
+ int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask,
+ int minRssi, int maxRssi) {
+ mLocalName = name;
+ mServiceUuid = uuid;
+ mServiceUuidMask = uuidMask;
+ mMacAddress = macAddress;
+ mServiceData = serviceData;
+ mServiceDataMask = serviceDataMask;
+ mManufacturerId = manufacturerId;
+ mManufacturerData = manufacturerData;
+ mManufacturerDataMask = manufacturerDataMask;
+ mMinRssi = minRssi;
+ mMaxRssi = maxRssi;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mLocalName == null ? 0 : 1);
+ if (mLocalName != null) {
+ dest.writeString(mLocalName);
+ }
+ dest.writeInt(mMacAddress == null ? 0 : 1);
+ if (mMacAddress != null) {
+ dest.writeString(mMacAddress);
+ }
+ dest.writeInt(mServiceUuid == null ? 0 : 1);
+ if (mServiceUuid != null) {
+ dest.writeParcelable(mServiceUuid, flags);
+ dest.writeInt(mServiceUuidMask == null ? 0 : 1);
+ if (mServiceUuidMask != null) {
+ dest.writeParcelable(mServiceUuidMask, flags);
+ }
+ }
+ dest.writeInt(mServiceData == null ? 0 : mServiceData.length);
+ if (mServiceData != null) {
+ dest.writeByteArray(mServiceData);
+ dest.writeInt(mServiceDataMask == null ? 0 : mServiceDataMask.length);
+ if (mServiceDataMask != null) {
+ dest.writeByteArray(mServiceDataMask);
+ }
+ }
+ dest.writeInt(mManufacturerId);
+ dest.writeInt(mManufacturerData == null ? 0 : mManufacturerData.length);
+ if (mManufacturerData != null) {
+ dest.writeByteArray(mManufacturerData);
+ dest.writeInt(mManufacturerDataMask == null ? 0 : mManufacturerDataMask.length);
+ if (mManufacturerDataMask != null) {
+ dest.writeByteArray(mManufacturerDataMask);
+ }
+ }
+ dest.writeInt(mMinRssi);
+ dest.writeInt(mMaxRssi);
+ }
+
+ /**
+ * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} form parcel.
+ */
+ public static final Creator<ScanFilter>
+ CREATOR = new Creator<ScanFilter>() {
+
+ @Override
+ public ScanFilter[] newArray(int size) {
+ return new ScanFilter[size];
+ }
+
+ @Override
+ public ScanFilter createFromParcel(Parcel in) {
+ Builder builder = new Builder();
+ if (in.readInt() == 1) {
+ builder.setName(in.readString());
+ }
+ if (in.readInt() == 1) {
+ builder.setMacAddress(in.readString());
+ }
+ if (in.readInt() == 1) {
+ ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader());
+ builder.setServiceUuid(uuid);
+ if (in.readInt() == 1) {
+ ParcelUuid uuidMask = in.readParcelable(
+ ParcelUuid.class.getClassLoader());
+ builder.setServiceUuid(uuid, uuidMask);
+ }
+ }
+
+ int serviceDataLength = in.readInt();
+ if (serviceDataLength > 0) {
+ byte[] serviceData = new byte[serviceDataLength];
+ in.readByteArray(serviceData);
+ builder.setServiceData(serviceData);
+ int serviceDataMaskLength = in.readInt();
+ if (serviceDataMaskLength > 0) {
+ byte[] serviceDataMask = new byte[serviceDataMaskLength];
+ in.readByteArray(serviceDataMask);
+ builder.setServiceData(serviceData, serviceDataMask);
+ }
+ }
+
+ int manufacturerId = in.readInt();
+ int manufacturerDataLength = in.readInt();
+ if (manufacturerDataLength > 0) {
+ byte[] manufacturerData = new byte[manufacturerDataLength];
+ in.readByteArray(manufacturerData);
+ builder.setManufacturerData(manufacturerId, manufacturerData);
+ int manufacturerDataMaskLength = in.readInt();
+ if (manufacturerDataMaskLength > 0) {
+ byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength];
+ in.readByteArray(manufacturerDataMask);
+ builder.setManufacturerData(manufacturerId, manufacturerData,
+ manufacturerDataMask);
+ }
+ }
+
+ int minRssi = in.readInt();
+ int maxRssi = in.readInt();
+ builder.setRssiRange(minRssi, maxRssi);
+ return builder.build();
+ }
+ };
+
+ /**
+ * Returns the filter set the local name field of Bluetooth advertisement data.
+ */
+ @Nullable
+ public String getLocalName() {
+ return mLocalName;
+ }
+
+ /**
+ * Returns the filter set on the service uuid.
+ */
+ @Nullable
+ public ParcelUuid getServiceUuid() {
+ return mServiceUuid;
+ }
+
+ @Nullable
+ public ParcelUuid getServiceUuidMask() {
+ return mServiceUuidMask;
+ }
+
+ @Nullable
+ public String getDeviceAddress() {
+ return mMacAddress;
+ }
+
+ @Nullable
+ public byte[] getServiceData() {
+ return mServiceData;
+ }
+
+ @Nullable
+ public byte[] getServiceDataMask() {
+ return mServiceDataMask;
+ }
+
+ /**
+ * Returns the manufacturer id. -1 if the manufacturer filter is not set.
+ */
+ public int getManufacturerId() {
+ return mManufacturerId;
+ }
+
+ @Nullable
+ public byte[] getManufacturerData() {
+ return mManufacturerData;
+ }
+
+ @Nullable
+ public byte[] getManufacturerDataMask() {
+ return mManufacturerDataMask;
+ }
+
+ /**
+ * Returns minimum value of rssi for the scan filter. {@link Integer#MIN_VALUE} if not set.
+ */
+ public int getMinRssi() {
+ return mMinRssi;
+ }
+
+ /**
+ * Returns maximum value of the rssi for the scan filter. {@link Integer#MAX_VALUE} if not set.
+ */
+ public int getMaxRssi() {
+ return mMaxRssi;
+ }
+
+ /**
+ * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match
+ * if it matches all the field filters.
+ */
+ public boolean matches(ScanResult scanResult) {
+ if (scanResult == null) {
+ return false;
+ }
+ BluetoothDevice device = scanResult.getDevice();
+ // Device match.
+ if (mMacAddress != null && (device == null || !mMacAddress.equals(device.getAddress()))) {
+ return false;
+ }
+
+ int rssi = scanResult.getRssi();
+ if (rssi < mMinRssi || rssi > mMaxRssi) {
+ return false;
+ }
+
+ byte[] scanRecordBytes = scanResult.getScanRecord();
+ ScanRecord scanRecord = ScanRecord.parseFromBytes(scanRecordBytes);
+
+ // Scan record is null but there exist filters on it.
+ if (scanRecord == null
+ && (mLocalName != null || mServiceUuid != null || mManufacturerData != null
+ || mServiceData != null)) {
+ return false;
+ }
+
+ // Local name match.
+ if (mLocalName != null && !mLocalName.equals(scanRecord.getLocalName())) {
+ return false;
+ }
+
+ // UUID match.
+ if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask,
+ scanRecord.getServiceUuids())) {
+ return false;
+ }
+
+ // Service data match
+ if (mServiceData != null &&
+ !matchesPartialData(mServiceData, mServiceDataMask, scanRecord.getServiceData())) {
+ return false;
+ }
+
+ // Manufacturer data match.
+ if (mManufacturerData != null && !matchesPartialData(mManufacturerData,
+ mManufacturerDataMask, scanRecord.getManufacturerSpecificData())) {
+ return false;
+ }
+ // All filters match.
+ return true;
+ }
+
+ // Check if the uuid pattern is contained in a list of parcel uuids.
+ private boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask,
+ List<ParcelUuid> uuids) {
+ if (uuid == null) {
+ return true;
+ }
+ if (uuids == null) {
+ return false;
+ }
+
+ for (ParcelUuid parcelUuid : uuids) {
+ UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid();
+ if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Check if the uuid pattern matches the particular service uuid.
+ private boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) {
+ if (mask == null) {
+ return uuid.equals(data);
+ }
+ if ((uuid.getLeastSignificantBits() & mask.getLeastSignificantBits()) !=
+ (data.getLeastSignificantBits() & mask.getLeastSignificantBits())) {
+ return false;
+ }
+ return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits()) ==
+ (data.getMostSignificantBits() & mask.getMostSignificantBits()));
+ }
+
+ // Check whether the data pattern matches the parsed data.
+ private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) {
+ if (dataMask == null) {
+ return Arrays.equals(data, parsedData);
+ }
+ if (parsedData == null) {
+ return false;
+ }
+ for (int i = 0; i < data.length; ++i) {
+ if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "BluetoothLeScanFilter [mLocalName=" + mLocalName + ", mMacAddress=" + mMacAddress
+ + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask + ", mServiceData="
+ + Arrays.toString(mServiceData) + ", mServiceDataMask="
+ + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId
+ + ", mManufacturerData=" + Arrays.toString(mManufacturerData)
+ + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask)
+ + ", mMinRssi=" + mMinRssi + ", mMaxRssi=" + mMaxRssi + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mLocalName, mMacAddress, mManufacturerId, mManufacturerData,
+ mManufacturerDataMask, mMaxRssi, mMinRssi, mServiceData, mServiceDataMask,
+ mServiceUuid, mServiceUuidMask);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ ScanFilter other = (ScanFilter) obj;
+ return Objects.equals(mLocalName, other.mLocalName) &&
+ Objects.equals(mMacAddress, other.mMacAddress) &&
+ mManufacturerId == other.mManufacturerId &&
+ Objects.deepEquals(mManufacturerData, other.mManufacturerData) &&
+ Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) &&
+ mMinRssi == other.mMinRssi && mMaxRssi == other.mMaxRssi &&
+ Objects.deepEquals(mServiceData, other.mServiceData) &&
+ Objects.deepEquals(mServiceDataMask, other.mServiceDataMask) &&
+ Objects.equals(mServiceUuid, other.mServiceUuid) &&
+ Objects.equals(mServiceUuidMask, other.mServiceUuidMask);
+ }
+
+ /**
+ * Builder class for {@link ScanFilter}.
+ */
+ public static final class Builder {
+
+ private String mLocalName;
+ private String mMacAddress;
+
+ private ParcelUuid mServiceUuid;
+ private ParcelUuid mUuidMask;
+
+ private byte[] mServiceData;
+ private byte[] mServiceDataMask;
+
+ private int mManufacturerId = -1;
+ private byte[] mManufacturerData;
+ private byte[] mManufacturerDataMask;
+
+ private int mMinRssi = Integer.MIN_VALUE;
+ private int mMaxRssi = Integer.MAX_VALUE;
+
+ /**
+ * Set filter on local name.
+ */
+ public Builder setName(String localName) {
+ mLocalName = localName;
+ return this;
+ }
+
+ /**
+ * Set filter on device mac address.
+ *
+ * @param macAddress The device mac address for the filter. It needs to be in the format of
+ * "01:02:03:AB:CD:EF". The mac address can be validated using
+ * {@link BluetoothAdapter#checkBluetoothAddress}.
+ * @throws IllegalArgumentException If the {@code macAddress} is invalid.
+ */
+ public Builder setMacAddress(String macAddress) {
+ if (macAddress != null && !BluetoothAdapter.checkBluetoothAddress(macAddress)) {
+ throw new IllegalArgumentException("invalid mac address " + macAddress);
+ }
+ mMacAddress = macAddress;
+ return this;
+ }
+
+ /**
+ * Set filter on service uuid.
+ */
+ public Builder setServiceUuid(ParcelUuid serviceUuid) {
+ mServiceUuid = serviceUuid;
+ mUuidMask = null; // clear uuid mask
+ return this;
+ }
+
+ /**
+ * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the
+ * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the
+ * bit in {@code serviceUuid}, and 0 to ignore that bit.
+ *
+ * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but
+ * {@code uuidMask} is not {@code null}.
+ */
+ public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) {
+ if (mUuidMask != null && mServiceUuid == null) {
+ throw new IllegalArgumentException("uuid is null while uuidMask is not null!");
+ }
+ mServiceUuid = serviceUuid;
+ mUuidMask = uuidMask;
+ return this;
+ }
+
+ /**
+ * Set filtering on service data.
+ */
+ public Builder setServiceData(byte[] serviceData) {
+ mServiceData = serviceData;
+ mServiceDataMask = null; // clear service data mask
+ return this;
+ }
+
+ /**
+ * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to
+ * match the one in service data, otherwise set it to 0 to ignore that bit.
+ * <p>
+ * The {@code serviceDataMask} must have the same length of the {@code serviceData}.
+ *
+ * @throws IllegalArgumentException If {@code serviceDataMask} is {@code null} while
+ * {@code serviceData} is not or {@code serviceDataMask} and {@code serviceData}
+ * has different length.
+ */
+ public Builder setServiceData(byte[] serviceData, byte[] serviceDataMask) {
+ if (mServiceDataMask != null) {
+ if (mServiceData == null) {
+ throw new IllegalArgumentException(
+ "serviceData is null while serviceDataMask is not null");
+ }
+ // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two
+ // byte array need to be the same.
+ if (mServiceData.length != mServiceDataMask.length) {
+ throw new IllegalArgumentException(
+ "size mismatch for service data and service data mask");
+ }
+ }
+ mServiceData = serviceData;
+ mServiceDataMask = serviceDataMask;
+ return this;
+ }
+
+ /**
+ * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id.
+ * <p>
+ * Note the first two bytes of the {@code manufacturerData} is the manufacturerId.
+ *
+ * @throws IllegalArgumentException If the {@code manufacturerId} is invalid.
+ */
+ public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) {
+ if (manufacturerData != null && manufacturerId < 0) {
+ throw new IllegalArgumentException("invalid manufacture id");
+ }
+ mManufacturerId = manufacturerId;
+ mManufacturerData = manufacturerData;
+ mManufacturerDataMask = null; // clear manufacturer data mask
+ return this;
+ }
+
+ /**
+ * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it
+ * needs to match the one in manufacturer data, otherwise set it to 0.
+ * <p>
+ * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}.
+ *
+ * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or
+ * {@code manufacturerData} is null while {@code manufacturerDataMask} is not,
+ * or {@code manufacturerData} and {@code manufacturerDataMask} have different
+ * length.
+ */
+ public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData,
+ byte[] manufacturerDataMask) {
+ if (manufacturerData != null && manufacturerId < 0) {
+ throw new IllegalArgumentException("invalid manufacture id");
+ }
+ if (mManufacturerDataMask != null) {
+ if (mManufacturerData == null) {
+ throw new IllegalArgumentException(
+ "manufacturerData is null while manufacturerDataMask is not null");
+ }
+ // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths
+ // of the two byte array need to be the same.
+ if (mManufacturerData.length != mManufacturerDataMask.length) {
+ throw new IllegalArgumentException(
+ "size mismatch for manufacturerData and manufacturerDataMask");
+ }
+ }
+ mManufacturerId = manufacturerId;
+ mManufacturerData = manufacturerData;
+ mManufacturerDataMask = manufacturerDataMask;
+ return this;
+ }
+
+ /**
+ * Set the desired rssi range for the filter. A scan result with rssi in the range of
+ * [minRssi, maxRssi] will be consider as a match.
+ */
+ public Builder setRssiRange(int minRssi, int maxRssi) {
+ mMinRssi = minRssi;
+ mMaxRssi = maxRssi;
+ return this;
+ }
+
+ /**
+ * Build {@link ScanFilter}.
+ *
+ * @throws IllegalArgumentException If the filter cannot be built.
+ */
+ public ScanFilter build() {
+ return new ScanFilter(mLocalName, mMacAddress,
+ mServiceUuid, mUuidMask,
+ mServiceData, mServiceDataMask,
+ mManufacturerId, mManufacturerData, mManufacturerDataMask, mMinRssi, mMaxRssi);
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/le/ScanRecord.java b/core/java/android/bluetooth/le/ScanRecord.java
new file mode 100644
index 0000000..bd7304b
--- /dev/null
+++ b/core/java/android/bluetooth/le/ScanRecord.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.le;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothUuid;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a scan record from Bluetooth LE scan.
+ */
+public final class ScanRecord {
+
+ private static final String TAG = "ScanRecord";
+
+ // The following data type values are assigned by Bluetooth SIG.
+ // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18.
+ private static final int DATA_TYPE_FLAGS = 0x01;
+ private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
+ private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
+ private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
+ private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
+ private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
+ private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
+ private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
+ private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
+ private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
+ private static final int DATA_TYPE_SERVICE_DATA = 0x16;
+ private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
+
+ // Flags of the advertising data.
+ private final int mAdvertiseFlags;
+
+ @Nullable
+ private final List<ParcelUuid> mServiceUuids;
+
+ private final int mManufacturerId;
+ @Nullable
+ private final byte[] mManufacturerSpecificData;
+
+ @Nullable
+ private final ParcelUuid mServiceDataUuid;
+ @Nullable
+ private final byte[] mServiceData;
+
+ // Transmission power level(in dB).
+ private final int mTxPowerLevel;
+
+ // Local name of the Bluetooth LE device.
+ private final String mLocalName;
+
+ /**
+ * Returns the advertising flags indicating the discoverable mode and capability of the device.
+ * Returns -1 if the flag field is not set.
+ */
+ public int getAdvertiseFlags() {
+ return mAdvertiseFlags;
+ }
+
+ /**
+ * Returns a list of service uuids within the advertisement that are used to identify the
+ * bluetooth gatt services.
+ */
+ public List<ParcelUuid> getServiceUuids() {
+ return mServiceUuids;
+ }
+
+ /**
+ * Returns the manufacturer identifier, which is a non-negative number assigned by Bluetooth
+ * SIG.
+ */
+ public int getManufacturerId() {
+ return mManufacturerId;
+ }
+
+ /**
+ * Returns the manufacturer specific data which is the content of manufacturer specific data
+ * field. The first 2 bytes of the data contain the company id.
+ */
+ public byte[] getManufacturerSpecificData() {
+ return mManufacturerSpecificData;
+ }
+
+ /**
+ * Returns a 16 bit uuid of the service that the service data is associated with.
+ */
+ public ParcelUuid getServiceDataUuid() {
+ return mServiceDataUuid;
+ }
+
+ /**
+ * Returns service data. The first two bytes should be a 16 bit service uuid associated with the
+ * service data.
+ */
+ public byte[] getServiceData() {
+ return mServiceData;
+ }
+
+ /**
+ * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE}
+ * if the field is not set. This value can be used to calculate the path loss of a received
+ * packet using the following equation:
+ * <p>
+ * <code>pathloss = txPowerLevel - rssi</code>
+ */
+ public int getTxPowerLevel() {
+ return mTxPowerLevel;
+ }
+
+ /**
+ * Returns the local name of the BLE device. The is a UTF-8 encoded string.
+ */
+ @Nullable
+ public String getLocalName() {
+ return mLocalName;
+ }
+
+ private ScanRecord(List<ParcelUuid> serviceUuids,
+ ParcelUuid serviceDataUuid, byte[] serviceData,
+ int manufacturerId,
+ byte[] manufacturerSpecificData, int advertiseFlags, int txPowerLevel,
+ String localName) {
+ mServiceUuids = serviceUuids;
+ mManufacturerId = manufacturerId;
+ mManufacturerSpecificData = manufacturerSpecificData;
+ mServiceDataUuid = serviceDataUuid;
+ mServiceData = serviceData;
+ mLocalName = localName;
+ mAdvertiseFlags = advertiseFlags;
+ mTxPowerLevel = txPowerLevel;
+ }
+
+ @Override
+ public String toString() {
+ return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids
+ + ", mManufacturerId=" + mManufacturerId + ", mManufacturerSpecificData="
+ + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid="
+ + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData)
+ + ", mTxPowerLevel=" + mTxPowerLevel + ", mLocalName=" + mLocalName + "]";
+ }
+
+ /**
+ * Parse scan record bytes to {@link ScanRecord}.
+ * <p>
+ * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
+ * <p>
+ * All numerical multi-byte entities and values shall use little-endian <strong>byte</strong>
+ * order.
+ *
+ * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response.
+ */
+ public static ScanRecord parseFromBytes(byte[] scanRecord) {
+ if (scanRecord == null) {
+ return null;
+ }
+
+ int currentPos = 0;
+ int advertiseFlag = -1;
+ List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>();
+ String localName = null;
+ int txPowerLevel = Integer.MIN_VALUE;
+ ParcelUuid serviceDataUuid = null;
+ byte[] serviceData = null;
+ int manufacturerId = -1;
+ byte[] manufacturerSpecificData = null;
+
+ try {
+ while (currentPos < scanRecord.length) {
+ // length is unsigned int.
+ int length = scanRecord[currentPos++] & 0xFF;
+ if (length == 0) {
+ break;
+ }
+ // Note the length includes the length of the field type itself.
+ int dataLength = length - 1;
+ // fieldType is unsigned int.
+ int fieldType = scanRecord[currentPos++] & 0xFF;
+ switch (fieldType) {
+ case DATA_TYPE_FLAGS:
+ advertiseFlag = scanRecord[currentPos] & 0xFF;
+ break;
+ case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
+ case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
+ parseServiceUuid(scanRecord, currentPos,
+ dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids);
+ break;
+ case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
+ case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
+ parseServiceUuid(scanRecord, currentPos, dataLength,
+ BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids);
+ break;
+ case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
+ case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
+ parseServiceUuid(scanRecord, currentPos, dataLength,
+ BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids);
+ break;
+ case DATA_TYPE_LOCAL_NAME_SHORT:
+ case DATA_TYPE_LOCAL_NAME_COMPLETE:
+ localName = new String(
+ extractBytes(scanRecord, currentPos, dataLength));
+ break;
+ case DATA_TYPE_TX_POWER_LEVEL:
+ txPowerLevel = scanRecord[currentPos];
+ break;
+ case DATA_TYPE_SERVICE_DATA:
+ serviceData = extractBytes(scanRecord, currentPos, dataLength);
+ // The first two bytes of the service data are service data uuid.
+ int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
+ byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
+ serviceUuidLength);
+ serviceDataUuid = BluetoothUuid.parseUuidFrom(serviceDataUuidBytes);
+ break;
+ case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
+ manufacturerSpecificData = extractBytes(scanRecord, currentPos,
+ dataLength);
+ // The first two bytes of the manufacturer specific data are
+ // manufacturer ids in little endian.
+ manufacturerId = ((manufacturerSpecificData[1] & 0xFF) << 8) +
+ (manufacturerSpecificData[0] & 0xFF);
+ break;
+ default:
+ // Just ignore, we don't handle such data type.
+ break;
+ }
+ currentPos += dataLength;
+ }
+
+ if (serviceUuids.isEmpty()) {
+ serviceUuids = null;
+ }
+ return new ScanRecord(serviceUuids, serviceDataUuid, serviceData,
+ manufacturerId, manufacturerSpecificData, advertiseFlag, txPowerLevel,
+ localName);
+ } catch (IndexOutOfBoundsException e) {
+ Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord));
+ return null;
+ }
+ }
+
+ // Parse service uuids.
+ private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
+ int uuidLength, List<ParcelUuid> serviceUuids) {
+ while (dataLength > 0) {
+ byte[] uuidBytes = extractBytes(scanRecord, currentPos,
+ uuidLength);
+ serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
+ dataLength -= uuidLength;
+ currentPos += uuidLength;
+ }
+ return currentPos;
+ }
+
+ // Helper method to extract bytes from byte array.
+ private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
+ byte[] bytes = new byte[length];
+ System.arraycopy(scanRecord, start, bytes, 0, length);
+ return bytes;
+ }
+}
diff --git a/core/java/android/bluetooth/le/ScanResult.aidl b/core/java/android/bluetooth/le/ScanResult.aidl
new file mode 100644
index 0000000..3943035
--- /dev/null
+++ b/core/java/android/bluetooth/le/ScanResult.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.bluetooth.le;
+
+parcelable ScanResult; \ No newline at end of file
diff --git a/core/java/android/bluetooth/le/ScanResult.java b/core/java/android/bluetooth/le/ScanResult.java
new file mode 100644
index 0000000..7e6e8f8
--- /dev/null
+++ b/core/java/android/bluetooth/le/ScanResult.java
@@ -0,0 +1,162 @@
+/*
+ * 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.bluetooth.le;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * ScanResult for Bluetooth LE scan.
+ */
+public final class ScanResult implements Parcelable {
+ // Remote bluetooth device.
+ private BluetoothDevice mDevice;
+
+ // Scan record, including advertising data and scan response data.
+ private byte[] mScanRecord;
+
+ // Received signal strength.
+ private int mRssi;
+
+ // Device timestamp when the result was last seen.
+ private long mTimestampNanos;
+
+ /**
+ * Constructor of scan result.
+ *
+ * @hide
+ */
+ public ScanResult(BluetoothDevice device, byte[] scanRecord, int rssi,
+ long timestampNanos) {
+ mDevice = device;
+ mScanRecord = scanRecord;
+ mRssi = rssi;
+ mTimestampNanos = timestampNanos;
+ }
+
+ private ScanResult(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mDevice != null) {
+ dest.writeInt(1);
+ mDevice.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mScanRecord != null) {
+ dest.writeInt(1);
+ dest.writeByteArray(mScanRecord);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(mRssi);
+ dest.writeLong(mTimestampNanos);
+ }
+
+ private void readFromParcel(Parcel in) {
+ if (in.readInt() == 1) {
+ mDevice = BluetoothDevice.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() == 1) {
+ mScanRecord = in.createByteArray();
+ }
+ mRssi = in.readInt();
+ mTimestampNanos = in.readLong();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Returns the remote bluetooth device identified by the bluetooth device address.
+ */
+ @Nullable
+ public BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ /**
+ * Returns the scan record, which can be a combination of advertisement and scan response.
+ */
+ @Nullable
+ public byte[] getScanRecord() {
+ return mScanRecord;
+ }
+
+ /**
+ * Returns the received signal strength in dBm. The valid range is [-127, 127].
+ */
+ public int getRssi() {
+ return mRssi;
+ }
+
+ /**
+ * Returns timestamp since boot when the scan record was observed.
+ */
+ public long getTimestampNanos() {
+ return mTimestampNanos;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampNanos);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ ScanResult other = (ScanResult) obj;
+ return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi) &&
+ Objects.deepEquals(mScanRecord, other.mScanRecord)
+ && (mTimestampNanos == other.mTimestampNanos);
+ }
+
+ @Override
+ public String toString() {
+ return "ScanResult{" + "mDevice=" + mDevice + ", mScanRecord="
+ + Arrays.toString(mScanRecord) + ", mRssi=" + mRssi + ", mTimestampNanos="
+ + mTimestampNanos + '}';
+ }
+
+ public static final Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() {
+ @Override
+ public ScanResult createFromParcel(Parcel source) {
+ return new ScanResult(source);
+ }
+
+ @Override
+ public ScanResult[] newArray(int size) {
+ return new ScanResult[size];
+ }
+ };
+
+}
diff --git a/core/java/android/bluetooth/le/ScanSettings.aidl b/core/java/android/bluetooth/le/ScanSettings.aidl
new file mode 100644
index 0000000..eb169c1
--- /dev/null
+++ b/core/java/android/bluetooth/le/ScanSettings.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.bluetooth.le;
+
+parcelable ScanSettings;
diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java
new file mode 100644
index 0000000..0a85675
--- /dev/null
+++ b/core/java/android/bluetooth/le/ScanSettings.java
@@ -0,0 +1,221 @@
+/*
+ * 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.bluetooth.le;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Settings for Bluetooth LE scan.
+ */
+public final class ScanSettings implements Parcelable {
+ /**
+ * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the
+ * least power.
+ */
+ public static final int SCAN_MODE_LOW_POWER = 0;
+ /**
+ * Perform Bluetooth LE scan in balanced power mode.
+ */
+ public static final int SCAN_MODE_BALANCED = 1;
+ /**
+ * Scan using highest duty cycle. It's recommended only using this mode when the application is
+ * running in foreground.
+ */
+ public static final int SCAN_MODE_LOW_LATENCY = 2;
+
+ /**
+ * Callback each time when a bluetooth advertisement is found.
+ */
+ public static final int CALLBACK_TYPE_ON_UPDATE = 0;
+ /**
+ * Callback when a bluetooth advertisement is found for the first time.
+ *
+ * @hide
+ */
+ public static final int CALLBACK_TYPE_ON_FOUND = 1;
+ /**
+ * Callback when a bluetooth advertisement is found for the first time, then lost.
+ *
+ * @hide
+ */
+ public static final int CALLBACK_TYPE_ON_LOST = 2;
+
+ /**
+ * Full scan result which contains device mac address, rssi, advertising and scan response and
+ * scan timestamp.
+ */
+ public static final int SCAN_RESULT_TYPE_FULL = 0;
+ /**
+ * Truncated scan result which contains device mac address, rssi and scan timestamp. Note it's
+ * possible for an app to get more scan results that it asks if there are multiple apps using
+ * this type. TODO: decide whether we could unhide this setting.
+ *
+ * @hide
+ */
+ public static final int SCAN_RESULT_TYPE_TRUNCATED = 1;
+
+ // Bluetooth LE scan mode.
+ private int mScanMode;
+
+ // Bluetooth LE scan callback type
+ private int mCallbackType;
+
+ // Bluetooth LE scan result type
+ private int mScanResultType;
+
+ // Time of delay for reporting the scan result
+ private long mReportDelayNanos;
+
+ public int getScanMode() {
+ return mScanMode;
+ }
+
+ public int getCallbackType() {
+ return mCallbackType;
+ }
+
+ public int getScanResultType() {
+ return mScanResultType;
+ }
+
+ /**
+ * Returns report delay timestamp based on the device clock.
+ */
+ public long getReportDelayNanos() {
+ return mReportDelayNanos;
+ }
+
+ private ScanSettings(int scanMode, int callbackType, int scanResultType,
+ long reportDelayNanos) {
+ mScanMode = scanMode;
+ mCallbackType = callbackType;
+ mScanResultType = scanResultType;
+ mReportDelayNanos = reportDelayNanos;
+ }
+
+ private ScanSettings(Parcel in) {
+ mScanMode = in.readInt();
+ mCallbackType = in.readInt();
+ mScanResultType = in.readInt();
+ mReportDelayNanos = in.readLong();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mScanMode);
+ dest.writeInt(mCallbackType);
+ dest.writeInt(mScanResultType);
+ dest.writeLong(mReportDelayNanos);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<ScanSettings>
+ CREATOR = new Creator<ScanSettings>() {
+ @Override
+ public ScanSettings[] newArray(int size) {
+ return new ScanSettings[size];
+ }
+
+ @Override
+ public ScanSettings createFromParcel(Parcel in) {
+ return new ScanSettings(in);
+ }
+ };
+
+ /**
+ * Builder for {@link ScanSettings}.
+ */
+ public static final class Builder {
+ private int mScanMode = SCAN_MODE_LOW_POWER;
+ private int mCallbackType = CALLBACK_TYPE_ON_UPDATE;
+ private int mScanResultType = SCAN_RESULT_TYPE_FULL;
+ private long mReportDelayNanos = 0;
+
+ /**
+ * Set scan mode for Bluetooth LE scan.
+ *
+ * @param scanMode The scan mode can be one of
+ * {@link ScanSettings#SCAN_MODE_LOW_POWER},
+ * {@link ScanSettings#SCAN_MODE_BALANCED} or
+ * {@link ScanSettings#SCAN_MODE_LOW_LATENCY}.
+ * @throws IllegalArgumentException If the {@code scanMode} is invalid.
+ */
+ public Builder setScanMode(int scanMode) {
+ if (scanMode < SCAN_MODE_LOW_POWER || scanMode > SCAN_MODE_LOW_LATENCY) {
+ throw new IllegalArgumentException("invalid scan mode " + scanMode);
+ }
+ mScanMode = scanMode;
+ return this;
+ }
+
+ /**
+ * Set callback type for Bluetooth LE scan.
+ *
+ * @param callbackType The callback type for the scan. Can only be
+ * {@link ScanSettings#CALLBACK_TYPE_ON_UPDATE}.
+ * @throws IllegalArgumentException If the {@code callbackType} is invalid.
+ */
+ public Builder setCallbackType(int callbackType) {
+ if (callbackType < CALLBACK_TYPE_ON_UPDATE
+ || callbackType > CALLBACK_TYPE_ON_LOST) {
+ throw new IllegalArgumentException("invalid callback type - " + callbackType);
+ }
+ mCallbackType = callbackType;
+ return this;
+ }
+
+ /**
+ * Set scan result type for Bluetooth LE scan.
+ *
+ * @param scanResultType Type for scan result, could be either
+ * {@link ScanSettings#SCAN_RESULT_TYPE_FULL} or
+ * {@link ScanSettings#SCAN_RESULT_TYPE_TRUNCATED}.
+ * @throws IllegalArgumentException If the {@code scanResultType} is invalid.
+ * @hide
+ */
+ public Builder setScanResultType(int scanResultType) {
+ if (scanResultType < SCAN_RESULT_TYPE_FULL
+ || scanResultType > SCAN_RESULT_TYPE_TRUNCATED) {
+ throw new IllegalArgumentException(
+ "invalid scanResultType - " + scanResultType);
+ }
+ mScanResultType = scanResultType;
+ return this;
+ }
+
+ /**
+ * Set report delay timestamp for Bluetooth LE scan.
+ */
+ public Builder setReportDelayNanos(long reportDelayNanos) {
+ mReportDelayNanos = reportDelayNanos;
+ return this;
+ }
+
+ /**
+ * Build {@link ScanSettings}.
+ */
+ public ScanSettings build() {
+ return new ScanSettings(mScanMode, mCallbackType, mScanResultType,
+ mReportDelayNanos);
+ }
+ }
+}
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 50c4fed..b44abf9 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -16,6 +16,7 @@
package android.content;
+import static android.content.ContentProvider.maybeAddUserId;
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.net.Uri;
@@ -186,7 +187,7 @@ public class ClipData implements Parcelable {
final CharSequence mText;
final String mHtmlText;
final Intent mIntent;
- final Uri mUri;
+ Uri mUri;
/**
* Create an Item consisting of a single block of (possibly styled) text.
@@ -809,6 +810,24 @@ public class ClipData implements Parcelable {
}
}
+ /**
+ * Prepare this {@link ClipData} to leave an app process.
+ *
+ * @hide
+ */
+ public void prepareToLeaveUser(int userId) {
+ final int size = mItems.size();
+ for (int i = 0; i < size; i++) {
+ final Item item = mItems.get(i);
+ if (item.mIntent != null) {
+ item.mIntent.prepareToLeaveUser(userId);
+ }
+ if (item.mUri != null) {
+ item.mUri = maybeAddUserId(item.mUri, userId);
+ }
+ }
+ }
+
@Override
public String toString() {
StringBuilder b = new StringBuilder(128);
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 02c850b..be9782f 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -36,6 +36,7 @@ import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
+import android.text.TextUtils;
import java.io.File;
import java.io.FileDescriptor;
@@ -195,6 +196,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
return rejectQuery(uri, projection, selection, selectionArgs, sortOrder,
CancellationSignal.fromTransport(cancellationSignal));
}
+ uri = getUriWithoutUserId(uri);
final String original = setCallingPackage(callingPkg);
try {
return ContentProvider.this.query(
@@ -207,6 +209,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
@Override
public String getType(Uri uri) {
+ uri = getUriWithoutUserId(uri);
return ContentProvider.this.getType(uri);
}
@@ -215,9 +218,11 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return rejectInsert(uri, initialValues);
}
+ int userId = getUserIdFromUri(uri);
+ uri = getUriWithoutUserId(uri);
final String original = setCallingPackage(callingPkg);
try {
- return ContentProvider.this.insert(uri, initialValues);
+ return maybeAddUserId(ContentProvider.this.insert(uri, initialValues), userId);
} finally {
setCallingPackage(original);
}
@@ -228,6 +233,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return 0;
}
+ uri = getUriWithoutUserId(uri);
final String original = setCallingPackage(callingPkg);
try {
return ContentProvider.this.bulkInsert(uri, initialValues);
@@ -240,24 +246,39 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
public ContentProviderResult[] applyBatch(String callingPkg,
ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
- for (ContentProviderOperation operation : operations) {
+ int numOperations = operations.size();
+ final int[] userIds = new int[numOperations];
+ for (int i = 0; i < numOperations; i++) {
+ ContentProviderOperation operation = operations.get(i);
+ userIds[i] = getUserIdFromUri(operation.getUri());
if (operation.isReadOperation()) {
if (enforceReadPermission(callingPkg, operation.getUri())
!= AppOpsManager.MODE_ALLOWED) {
throw new OperationApplicationException("App op not allowed", 0);
}
}
-
if (operation.isWriteOperation()) {
if (enforceWritePermission(callingPkg, operation.getUri())
!= AppOpsManager.MODE_ALLOWED) {
throw new OperationApplicationException("App op not allowed", 0);
}
}
+ if (userIds[i] != UserHandle.USER_CURRENT) {
+ // Removing the user id from the uri.
+ operation = new ContentProviderOperation(operation, true);
+ }
+ operations.set(i, operation);
}
final String original = setCallingPackage(callingPkg);
try {
- return ContentProvider.this.applyBatch(operations);
+ ContentProviderResult[] results = ContentProvider.this.applyBatch(operations);
+ for (int i = 0; i < results.length ; i++) {
+ if (userIds[i] != UserHandle.USER_CURRENT) {
+ // Adding the userId to the uri.
+ results[i] = new ContentProviderResult(results[i], userIds[i]);
+ }
+ }
+ return results;
} finally {
setCallingPackage(original);
}
@@ -268,6 +289,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return 0;
}
+ uri = getUriWithoutUserId(uri);
final String original = setCallingPackage(callingPkg);
try {
return ContentProvider.this.delete(uri, selection, selectionArgs);
@@ -282,6 +304,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return 0;
}
+ uri = getUriWithoutUserId(uri);
final String original = setCallingPackage(callingPkg);
try {
return ContentProvider.this.update(uri, values, selection, selectionArgs);
@@ -295,6 +318,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal)
throws FileNotFoundException {
enforceFilePermission(callingPkg, uri, mode);
+ uri = getUriWithoutUserId(uri);
final String original = setCallingPackage(callingPkg);
try {
return ContentProvider.this.openFile(
@@ -309,6 +333,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal)
throws FileNotFoundException {
enforceFilePermission(callingPkg, uri, mode);
+ uri = getUriWithoutUserId(uri);
final String original = setCallingPackage(callingPkg);
try {
return ContentProvider.this.openAssetFile(
@@ -330,6 +355,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
@Override
public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+ uri = getUriWithoutUserId(uri);
return ContentProvider.this.getStreamTypes(uri, mimeTypeFilter);
}
@@ -337,6 +363,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType,
Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException {
enforceFilePermission(callingPkg, uri, "r");
+ uri = getUriWithoutUserId(uri);
final String original = setCallingPackage(callingPkg);
try {
return ContentProvider.this.openTypedAssetFile(
@@ -356,9 +383,11 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return null;
}
+ int userId = getUserIdFromUri(uri);
+ uri = getUriWithoutUserId(uri);
final String original = setCallingPackage(callingPkg);
try {
- return ContentProvider.this.canonicalize(uri);
+ return maybeAddUserId(ContentProvider.this.canonicalize(uri), userId);
} finally {
setCallingPackage(original);
}
@@ -369,9 +398,11 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return null;
}
+ int userId = getUserIdFromUri(uri);
+ uri = getUriWithoutUserId(uri);
final String original = setCallingPackage(callingPkg);
try {
- return ContentProvider.this.uncanonicalize(uri);
+ return maybeAddUserId(ContentProvider.this.uncanonicalize(uri), userId);
} finally {
setCallingPackage(original);
}
@@ -1680,4 +1711,75 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
writer.println("nothing to dump");
}
+
+ /** @hide */
+ public static int getUserIdFromAuthority(String auth, int defaultUserId) {
+ if (auth == null) return defaultUserId;
+ int end = auth.indexOf('@');
+ if (end == -1) return defaultUserId;
+ String userIdString = auth.substring(0, end);
+ try {
+ return Integer.parseInt(userIdString);
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "Error parsing userId.", e);
+ return UserHandle.USER_NULL;
+ }
+ }
+
+ /** @hide */
+ public static int getUserIdFromAuthority(String auth) {
+ return getUserIdFromAuthority(auth, UserHandle.USER_CURRENT);
+ }
+
+ /** @hide */
+ public static int getUserIdFromUri(Uri uri, int defaultUserId) {
+ if (uri == null) return defaultUserId;
+ return getUserIdFromAuthority(uri.getAuthority(), defaultUserId);
+ }
+
+ /** @hide */
+ public static int getUserIdFromUri(Uri uri) {
+ return getUserIdFromUri(uri, UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Removes userId part from authority string. Expects format:
+ * userId@some.authority
+ * If there is no userId in the authority, it symply returns the argument
+ * @hide
+ */
+ public static String getAuthorityWithoutUserId(String auth) {
+ if (auth == null) return null;
+ int end = auth.indexOf('@');
+ return auth.substring(end+1);
+ }
+
+ /** @hide */
+ public static Uri getUriWithoutUserId(Uri uri) {
+ if (uri == null) return null;
+ Uri.Builder builder = uri.buildUpon();
+ builder.authority(getAuthorityWithoutUserId(uri.getAuthority()));
+ return builder.build();
+ }
+
+ /** @hide */
+ public static boolean uriHasUserId(Uri uri) {
+ if (uri == null) return false;
+ return !TextUtils.isEmpty(uri.getUserInfo());
+ }
+
+ /** @hide */
+ public static Uri maybeAddUserId(Uri uri, int userId) {
+ if (uri == null) return null;
+ if (userId != UserHandle.USER_CURRENT
+ && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+ if (!uriHasUserId(uri)) {
+ //We don't add the user Id if there's already one
+ Uri.Builder builder = uri.buildUpon();
+ builder.encodedAuthority("" + userId + "@" + uri.getEncodedAuthority());
+ return builder.build();
+ }
+ }
+ return uri;
+ }
}
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index 12e9bab..136e54d 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -16,6 +16,7 @@
package android.content;
+import android.content.ContentProvider;
import android.database.Cursor;
import android.net.Uri;
import android.os.Parcel;
@@ -87,6 +88,31 @@ public class ContentProviderOperation implements Parcelable {
mYieldAllowed = source.readInt() != 0;
}
+ /** @hide */
+ public ContentProviderOperation(ContentProviderOperation cpo, boolean removeUserIdFromUri) {
+ mType = cpo.mType;
+ if (removeUserIdFromUri) {
+ mUri = ContentProvider.getUriWithoutUserId(cpo.mUri);
+ } else {
+ mUri = cpo.mUri;
+ }
+ mValues = cpo.mValues;
+ mSelection = cpo.mSelection;
+ mSelectionArgs = cpo.mSelectionArgs;
+ mExpectedCount = cpo.mExpectedCount;
+ mSelectionArgsBackReferences = cpo.mSelectionArgsBackReferences;
+ mValuesBackReferences = cpo.mValuesBackReferences;
+ mYieldAllowed = cpo.mYieldAllowed;
+ }
+
+ /** @hide */
+ public ContentProviderOperation getWithoutUserIdInUri() {
+ if (ContentProvider.uriHasUserId(mUri)) {
+ return new ContentProviderOperation(this, true);
+ }
+ return this;
+ }
+
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
Uri.writeToParcel(dest, mUri);
@@ -387,7 +413,6 @@ public class ContentProviderOperation implements Parcelable {
}
};
-
/**
* Used to add parameters to a {@link ContentProviderOperation}. The {@link Builder} is
* first created by calling {@link ContentProviderOperation#newInsert(android.net.Uri)},
diff --git a/core/java/android/content/ContentProviderResult.java b/core/java/android/content/ContentProviderResult.java
index 5d188ef..ec3d002 100644
--- a/core/java/android/content/ContentProviderResult.java
+++ b/core/java/android/content/ContentProviderResult.java
@@ -16,7 +16,9 @@
package android.content;
+import android.content.ContentProvider;
import android.net.Uri;
+import android.os.UserHandle;
import android.os.Parcelable;
import android.os.Parcel;
@@ -50,6 +52,12 @@ public class ContentProviderResult implements Parcelable {
}
}
+ /** @hide */
+ public ContentProviderResult(ContentProviderResult cpr, int userId) {
+ uri = ContentProvider.maybeAddUserId(cpr.uri, userId);
+ count = cpr.count;
+ }
+
public void writeToParcel(Parcel dest, int flags) {
if (uri == null) {
dest.writeInt(1);
@@ -81,4 +89,4 @@ public class ContentProviderResult implements Parcelable {
}
return "ContentProviderResult(count=" + count + ")";
}
-} \ No newline at end of file
+}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 5b41394..7642e13 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -1643,7 +1643,8 @@ public abstract class ContentResolver {
*/
public void takePersistableUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags) {
try {
- ActivityManagerNative.getDefault().takePersistableUriPermission(uri, modeFlags);
+ ActivityManagerNative.getDefault().takePersistableUriPermission(
+ ContentProvider.getUriWithoutUserId(uri), modeFlags, resolveUserId(uri));
} catch (RemoteException e) {
}
}
@@ -1658,7 +1659,8 @@ public abstract class ContentResolver {
*/
public void releasePersistableUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags) {
try {
- ActivityManagerNative.getDefault().releasePersistableUriPermission(uri, modeFlags);
+ ActivityManagerNative.getDefault().releasePersistableUriPermission(
+ ContentProvider.getUriWithoutUserId(uri), modeFlags, resolveUserId(uri));
} catch (RemoteException e) {
}
}
@@ -2462,4 +2464,9 @@ public abstract class ContentResolver {
private final Context mContext;
final String mPackageName;
private static final String TAG = "ContentResolver";
+
+ /** @hide */
+ public int resolveUserId(Uri uri) {
+ return ContentProvider.getUserIdFromUri(uri, mContext.getUserId());
+ }
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a059e48..a040efb 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -40,6 +40,7 @@ import android.os.Looper;
import android.os.StatFs;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.MediaStore;
import android.util.AttributeSet;
import android.view.DisplayAdjustments;
import android.view.Display;
@@ -929,6 +930,40 @@ public abstract class Context {
public abstract File[] getExternalCacheDirs();
/**
+ * Returns absolute paths to application-specific directories on all
+ * external storage devices where the application can place media files.
+ * These files are scanned and made available to other apps through
+ * {@link MediaStore}.
+ * <p>
+ * This is like {@link #getExternalFilesDirs} in that these files will be
+ * deleted when the application is uninstalled, however there are some
+ * important differences:
+ * <ul>
+ * <li>External files are not always available: they will disappear if the
+ * user mounts the external storage on a computer or removes it.
+ * <li>There is no security enforced with these files.
+ * </ul>
+ * <p>
+ * External storage devices returned here are considered a permanent part of
+ * the device, including both emulated external storage and physical media
+ * slots, such as SD cards in a battery compartment. The returned paths do
+ * not include transient devices, such as USB flash drives.
+ * <p>
+ * An application may store data on any or all of the returned devices. For
+ * example, an app may choose to store large files on the device with the
+ * most available space, as measured by {@link StatFs}.
+ * <p>
+ * No permissions are required to read or write to the returned paths; they
+ * are always accessible to the calling app. Write access outside of these
+ * paths on secondary external storage devices is not available.
+ * <p>
+ * Returned paths may be {@code null} if a storage device is unavailable.
+ *
+ * @see Environment#getExternalStorageState(File)
+ */
+ public abstract File[] getExternalMediaDirs();
+
+ /**
* Returns an array of strings naming the private files associated with
* this Context's application package.
*
@@ -1499,6 +1534,17 @@ public abstract class Context {
@Nullable Bundle initialExtras);
/**
+ * Similar to above but takes an appOp as well, to enforce restrictions.
+ * @see #sendOrderedBroadcastAsUser(Intent, UserHandle, String,
+ * BroadcastReceiver, Handler, int, String, Bundle)
+ * @hide
+ */
+ public abstract void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ @Nullable String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
+ @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+ @Nullable Bundle initialExtras);
+
+ /**
* Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the
* Intent you are sending stays around after the broadcast is complete,
* so that others can quickly retrieve that data through the return
@@ -1980,9 +2026,10 @@ public abstract class Context {
//@hide: NETWORK_STATS_SERVICE,
//@hide: NETWORK_POLICY_SERVICE,
WIFI_SERVICE,
- WIFI_HOTSPOT_SERVICE,
+ WIFI_PASSPOINT_SERVICE,
WIFI_P2P_SERVICE,
WIFI_SCANNING_SERVICE,
+ //@hide: ETHERNET_SERVICE,
NSD_SERVICE,
AUDIO_SERVICE,
MEDIA_ROUTER_SERVICE,
@@ -2011,6 +2058,7 @@ public abstract class Context {
PRINT_SERVICE,
MEDIA_SESSION_SERVICE,
BATTERY_SERVICE,
+ TASK_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ServiceName {}
@@ -2067,6 +2115,8 @@ public abstract class Context {
* <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
+ * <dt> {@link #TASK_SERVICE} ("taskmanager")
+ * <dd> A {@link android.app.task.TaskManager} for managing scheduled tasks
* </dl>
*
* <p>Note: System services obtained via this API may be closely associated with
@@ -2122,6 +2172,8 @@ public abstract class Context {
* @see android.app.DownloadManager
* @see #BATTERY_SERVICE
* @see android.os.BatteryManager
+ * @see #TASK_SERVICE
+ * @see android.app.task.TaskManager
*/
public abstract Object getSystemService(@ServiceName @NonNull String name);
@@ -2341,13 +2393,14 @@ 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.
+ * android.net.wifi.passpoint.WifiPasspointManager} for handling management of
+ * Wi-Fi passpoint access.
*
* @see #getSystemService
- * @see android.net.wifi.hotspot.WifiHotspotManager
+ * @see android.net.wifi.passpoint.WifiPasspointManager
+ * @hide
*/
- public static final String WIFI_HOTSPOT_SERVICE = "wifihotspot";
+ public static final String WIFI_PASSPOINT_SERVICE = "wifipasspoint";
/**
* Use with {@link #getSystemService} to retrieve a {@link
@@ -2371,6 +2424,18 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.ethernet.EthernetManager} for handling management of
+ * Ethernet access.
+ *
+ * @see #getSystemService
+ * @see android.net.ethernet.EthernetManager
+ *
+ * @hide
+ */
+ public static final String ETHERNET_SERVICE = "ethernet";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
* android.net.nsd.NsdManager} for handling management of network service
* discovery
*
@@ -2411,10 +2476,10 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
- * {@link android.media.session.SessionManager} for managing media Sessions.
+ * {@link android.media.session.MediaSessionManager} for managing media Sessions.
*
* @see #getSystemService
- * @see android.media.session.SessionManager
+ * @see android.media.session.MediaSessionManager
*/
public static final String MEDIA_SESSION_SERVICE = "media_session";
@@ -2572,13 +2637,24 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
- * {@link android.hardware.hdmi.HdmiCecManager for controlling and managing
+ * {@link android.hardware.hdmi.HdmiCecManager} for controlling and managing
* HDMI-CEC protocol.
*
* @see #getSystemService
* @see android.hardware.hdmi.HdmiCecManager
*/
- public static final String HDMI_CEC_SERVICE = "hdmi_cec";
+ // TODO: Remove this once HdmiControlService is ready.
+ public static final String HDMI_CEC_SERVICE = "hdmi_cec";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.hardware.hdmi.HdmiControlManager} for controlling and managing
+ * HDMI-CEC protocol.
+ *
+ * @see #getSystemService
+ * @see android.hardware.hdmi.HdmiControlManager
+ */
+ public static final String HDMI_CONTROL_SERVICE = "hdmi_control";
/**
* Use with {@link #getSystemService} to retrieve a
@@ -2619,6 +2695,15 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.content.RestrictionsManager} for retrieving application restrictions
+ * and requesting permissions for restricted operations.
+ * @see #getSystemService
+ * @see android.content.RestrictionsManager
+ */
+ public static final String RESTRICTIONS_SERVICE = "restrictions";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.app.AppOpsManager} for tracking application operations
* on the device.
*
@@ -2666,11 +2751,11 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
- * {@link android.tv.TvInputManager} for interacting with TV inputs on the
- * device.
+ * {@link android.media.tv.TvInputManager} for interacting with TV inputs
+ * on the device.
*
* @see #getSystemService
- * @see android.tv.TvInputManager
+ * @see android.media.tv.TvInputManager
*/
public static final String TV_INPUT_SERVICE = "tv_input";
@@ -2693,6 +2778,15 @@ public abstract class Context {
public static final String USAGE_STATS_SERVICE = "usagestats";
/**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.app.task.TaskManager} instance for managing occasional
+ * background tasks.
+ * @see #getSystemService
+ * @see android.app.task.TaskManager
+ */
+ public static final String TASK_SERVICE = "task";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 93f6cdf..dbf9122 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -237,6 +237,11 @@ public class ContextWrapper extends Context {
}
@Override
+ public File[] getExternalMediaDirs() {
+ return mBase.getExternalMediaDirs();
+ }
+
+ @Override
public File getDir(String name, int mode) {
return mBase.getDir(name, mode);
}
@@ -418,6 +423,16 @@ public class ContextWrapper extends Context {
scheduler, initialCode, initialData, initialExtras);
}
+ /** @hide */
+ @Override
+ public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
+ Handler scheduler,
+ int initialCode, String initialData, Bundle initialExtras) {
+ mBase.sendOrderedBroadcastAsUser(intent, user, receiverPermission, appOp, resultReceiver,
+ scheduler, initialCode, initialData, initialExtras);
+ }
+
@Override
public void sendStickyBroadcast(Intent intent) {
mBase.sendStickyBroadcast(intent);
diff --git a/core/java/android/content/IRestrictionsManager.aidl b/core/java/android/content/IRestrictionsManager.aidl
new file mode 100644
index 0000000..b1c0a3a
--- /dev/null
+++ b/core/java/android/content/IRestrictionsManager.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;
+
+import android.os.Bundle;
+
+/**
+ * Interface used by the RestrictionsManager
+ * @hide
+ */
+interface IRestrictionsManager {
+ Bundle getApplicationRestrictions(in String packageName);
+ boolean hasRestrictionsProvider();
+ void requestPermission(in String packageName, in String requestTemplate, in Bundle requestData);
+ void notifyPermissionResponse(in String packageName, in Bundle response);
+}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 3cfc56c..bd07470 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -26,6 +26,7 @@ import android.annotation.IntDef;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.pm.ActivityInfo;
+import static android.content.ContentProvider.maybeAddUserId;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
@@ -44,6 +45,7 @@ import android.util.AttributeSet;
import android.util.Log;
import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.Serializable;
@@ -603,6 +605,15 @@ import java.util.Set;
* of all possible flags.
*/
public class Intent implements Parcelable, Cloneable {
+ private static final String ATTR_ACTION = "action";
+ private static final String TAG_CATEGORIES = "categories";
+ private static final String ATTR_CATEGORY = "category";
+ private static final String TAG_EXTRA = "extra";
+ private static final String ATTR_TYPE = "type";
+ private static final String ATTR_COMPONENT = "component";
+ private static final String ATTR_DATA = "data";
+ private static final String ATTR_FLAGS = "flags";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent activity actions (see action variable).
@@ -3728,32 +3739,27 @@ 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} <code>standard</code>
- * or <code>singleTop</code>.
+ * This flag is used to open a document into a new task rooted at the activity launched
+ * by this Intent. Through the use of this flag, or its equivalent attribute,
+ * {@link android.R.attr#documentLaunchMode} multiple instances of the same activity
+ * containing different douments will appear in the recent tasks list.
*
- * <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>The use of the activity attribute form of this,
+ * {@link android.R.attr#documentLaunchMode}, is
+ * preferred over the Intent flag described here. The attribute form allows the
+ * Activity to specify multiple document behavior for all launchers of the Activity
+ * whereas using this flag requires each Intent that launches the Activity to specify it.
*
- * <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.
+ * <p>FLAG_ACTIVITY_NEW_DOCUMENT may be used in conjunction with {@link
+ * #FLAG_ACTIVITY_MULTIPLE_TASK}. When used alone it is the
+ * equivalent of the Activity manifest specifying {@link
+ * android.R.attr#documentLaunchMode}="intoExisting". When used with
+ * FLAG_ACTIVITY_MULTIPLE_TASK it is the equivalent of the Activity manifest specifying
+ * {@link android.R.attr#documentLaunchMode}="always".
*
- * <p>This is equivalent to the attribute {@link android.R.attr#documentLaunchMode}.
+ * Refer to {@link android.R.attr#documentLaunchMode} for more information.
*
+ * @see android.R.attr#documentLaunchMode
* @see #FLAG_ACTIVITY_MULTIPLE_TASK
*/
public static final int FLAG_ACTIVITY_NEW_DOCUMENT =
@@ -7346,7 +7352,7 @@ public class Intent implements Parcelable, Cloneable {
}
String nodeName = parser.getName();
- if (nodeName.equals("category")) {
+ if (nodeName.equals(TAG_CATEGORIES)) {
sa = resources.obtainAttributes(attrs,
com.android.internal.R.styleable.IntentCategory);
String cat = sa.getString(com.android.internal.R.styleable.IntentCategory_name);
@@ -7357,11 +7363,11 @@ public class Intent implements Parcelable, Cloneable {
}
XmlUtils.skipCurrentTag(parser);
- } else if (nodeName.equals("extra")) {
+ } else if (nodeName.equals(TAG_EXTRA)) {
if (intent.mExtras == null) {
intent.mExtras = new Bundle();
}
- resources.parseBundleExtra("extra", attrs, intent.mExtras);
+ resources.parseBundleExtra(TAG_EXTRA, attrs, intent.mExtras);
XmlUtils.skipCurrentTag(parser);
} else {
@@ -7372,6 +7378,76 @@ public class Intent implements Parcelable, Cloneable {
return intent;
}
+ /** @hide */
+ public void saveToXml(XmlSerializer out) throws IOException {
+ if (mAction != null) {
+ out.attribute(null, ATTR_ACTION, mAction);
+ }
+ if (mData != null) {
+ out.attribute(null, ATTR_DATA, mData.toString());
+ }
+ if (mType != null) {
+ out.attribute(null, ATTR_TYPE, mType);
+ }
+ if (mComponent != null) {
+ out.attribute(null, ATTR_COMPONENT, mComponent.flattenToShortString());
+ }
+ out.attribute(null, ATTR_FLAGS, Integer.toHexString(getFlags()));
+
+ if (mCategories != null) {
+ out.startTag(null, TAG_CATEGORIES);
+ for (int categoryNdx = mCategories.size() - 1; categoryNdx >= 0; --categoryNdx) {
+ out.attribute(null, ATTR_CATEGORY, mCategories.valueAt(categoryNdx));
+ }
+ }
+ }
+
+ /** @hide */
+ public static Intent restoreFromXml(XmlPullParser in) throws IOException,
+ XmlPullParserException {
+ Intent intent = new Intent();
+ final int outerDepth = in.getDepth();
+
+ int attrCount = in.getAttributeCount();
+ for (int attrNdx = attrCount - 1; attrNdx >= 0; --attrNdx) {
+ final String attrName = in.getAttributeName(attrNdx);
+ final String attrValue = in.getAttributeValue(attrNdx);
+ if (ATTR_ACTION.equals(attrName)) {
+ intent.setAction(attrValue);
+ } else if (ATTR_DATA.equals(attrName)) {
+ intent.setData(Uri.parse(attrValue));
+ } else if (ATTR_TYPE.equals(attrName)) {
+ intent.setType(attrValue);
+ } else if (ATTR_COMPONENT.equals(attrName)) {
+ intent.setComponent(ComponentName.unflattenFromString(attrValue));
+ } else if (ATTR_FLAGS.equals(attrName)) {
+ intent.setFlags(Integer.valueOf(attrValue, 16));
+ } else {
+ Log.e("Intent", "restoreFromXml: unknown attribute=" + attrName);
+ }
+ }
+
+ int event;
+ String name;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+ (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
+ if (event == XmlPullParser.START_TAG) {
+ name = in.getName();
+ if (TAG_CATEGORIES.equals(name)) {
+ attrCount = in.getAttributeCount();
+ for (int attrNdx = attrCount - 1; attrNdx >= 0; --attrNdx) {
+ intent.addCategory(in.getAttributeValue(attrNdx));
+ }
+ } else {
+ Log.w("Intent", "restoreFromXml: unknown name=" + name);
+ XmlUtils.skipCurrentTag(in);
+ }
+ }
+ }
+
+ return intent;
+ }
+
/**
* Normalize a MIME data type.
*
@@ -7433,6 +7509,41 @@ public class Intent implements Parcelable, Cloneable {
}
/**
+ * Prepare this {@link Intent} to be sent to another user
+ *
+ * @hide
+ */
+ public void prepareToLeaveUser(int userId) {
+ Uri data = getData();
+ if (data != null) {
+ mData = maybeAddUserId(data, userId);
+ }
+ if (mSelector != null) {
+ mSelector.prepareToLeaveUser(userId);
+ }
+ if (mClipData != null) {
+ mClipData.prepareToLeaveUser(userId);
+ }
+ String action = getAction();
+ if (ACTION_SEND.equals(action)) {
+ final Uri stream = getParcelableExtra(EXTRA_STREAM);
+ if (stream != null) {
+ putExtra(EXTRA_STREAM, maybeAddUserId(stream, userId));
+ }
+ }
+ if (ACTION_SEND_MULTIPLE.equals(action)) {
+ final ArrayList<Uri> streams = getParcelableArrayListExtra(EXTRA_STREAM);
+ if (streams != null) {
+ ArrayList<Uri> newStreams = new ArrayList<Uri>();
+ for (int i = 0; i < streams.size(); i++) {
+ newStreams.add(maybeAddUserId(streams.get(i), userId));
+ }
+ putParcelableArrayListExtra(EXTRA_STREAM, newStreams);
+ }
+ }
+ }
+
+ /**
* Migrate any {@link #EXTRA_STREAM} in {@link #ACTION_SEND} and
* {@link #ACTION_SEND_MULTIPLE} to {@link ClipData}. Also inspects nested
* intents in {@link #ACTION_CHOOSER}.
diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java
index 3ff53bf..62f88a9 100644
--- a/core/java/android/content/RestrictionEntry.java
+++ b/core/java/android/content/RestrictionEntry.java
@@ -73,32 +73,38 @@ public class RestrictionEntry implements Parcelable {
*/
public static final int TYPE_MULTI_SELECT = 4;
+ /**
+ * A type of restriction. Use this for storing an integer value. The range of values
+ * is from {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}.
+ */
+ public static final int TYPE_INTEGER = 5;
+
/** The type of restriction. */
- private int type;
+ private int mType;
/** The unique key that identifies the restriction. */
- private String key;
+ private String mKey;
/** The user-visible title of the restriction. */
- private String title;
+ private String mTitle;
/** The user-visible secondary description of the restriction. */
- private String description;
+ private String mDescription;
/** The user-visible set of choices used for single-select and multi-select lists. */
- private String [] choices;
+ private String [] mChoiceEntries;
/** The values corresponding to the user-visible choices. The value(s) of this entry will
* one or more of these, returned by {@link #getAllSelectedStrings()} and
* {@link #getSelectedString()}.
*/
- private String [] values;
+ private String [] mChoiceValues;
/* The chosen value, whose content depends on the type of the restriction. */
- private String currentValue;
+ private String mCurrentValue;
/* List of selected choices in the multi-select case. */
- private String[] currentValues;
+ private String[] mCurrentValues;
/**
* Constructor for {@link #TYPE_CHOICE} type.
@@ -106,9 +112,9 @@ public class RestrictionEntry implements Parcelable {
* @param selectedString the current value
*/
public RestrictionEntry(String key, String selectedString) {
- this.key = key;
- this.type = TYPE_CHOICE;
- this.currentValue = selectedString;
+ this.mKey = key;
+ this.mType = TYPE_CHOICE;
+ this.mCurrentValue = selectedString;
}
/**
@@ -117,8 +123,8 @@ public class RestrictionEntry implements Parcelable {
* @param selectedState whether this restriction is selected or not
*/
public RestrictionEntry(String key, boolean selectedState) {
- this.key = key;
- this.type = TYPE_BOOLEAN;
+ this.mKey = key;
+ this.mType = TYPE_BOOLEAN;
setSelectedState(selectedState);
}
@@ -128,9 +134,20 @@ public class RestrictionEntry implements Parcelable {
* @param selectedStrings the list of values that are currently selected
*/
public RestrictionEntry(String key, String[] selectedStrings) {
- this.key = key;
- this.type = TYPE_MULTI_SELECT;
- this.currentValues = selectedStrings;
+ this.mKey = key;
+ this.mType = TYPE_MULTI_SELECT;
+ this.mCurrentValues = selectedStrings;
+ }
+
+ /**
+ * Constructor for {@link #TYPE_INTEGER} type.
+ * @param key the unique key for this restriction
+ * @param selectedInt the integer value of the restriction
+ */
+ public RestrictionEntry(String key, int selectedInt) {
+ mKey = key;
+ mType = TYPE_INTEGER;
+ setIntValue(selectedInt);
}
/**
@@ -138,7 +155,7 @@ public class RestrictionEntry implements Parcelable {
* @param type the type for this restriction.
*/
public void setType(int type) {
- this.type = type;
+ this.mType = type;
}
/**
@@ -146,7 +163,7 @@ public class RestrictionEntry implements Parcelable {
* @return the type for this restriction
*/
public int getType() {
- return type;
+ return mType;
}
/**
@@ -155,7 +172,7 @@ public class RestrictionEntry implements Parcelable {
* single string values.
*/
public String getSelectedString() {
- return currentValue;
+ return mCurrentValue;
}
/**
@@ -164,7 +181,7 @@ public class RestrictionEntry implements Parcelable {
* null otherwise.
*/
public String[] getAllSelectedStrings() {
- return currentValues;
+ return mCurrentValues;
}
/**
@@ -172,7 +189,23 @@ public class RestrictionEntry implements Parcelable {
* @return the current selected state of the entry.
*/
public boolean getSelectedState() {
- return Boolean.parseBoolean(currentValue);
+ return Boolean.parseBoolean(mCurrentValue);
+ }
+
+ /**
+ * Returns the value of the entry as an integer when the type is {@link #TYPE_INTEGER}.
+ * @return the integer value of the entry.
+ */
+ public int getIntValue() {
+ return Integer.parseInt(mCurrentValue);
+ }
+
+ /**
+ * Sets the integer value of the entry when the type is {@link #TYPE_INTEGER}.
+ * @param value the integer value to set.
+ */
+ public void setIntValue(int value) {
+ mCurrentValue = Integer.toString(value);
}
/**
@@ -181,7 +214,7 @@ public class RestrictionEntry implements Parcelable {
* @param selectedString the string value to select.
*/
public void setSelectedString(String selectedString) {
- currentValue = selectedString;
+ mCurrentValue = selectedString;
}
/**
@@ -190,7 +223,7 @@ public class RestrictionEntry implements Parcelable {
* @param state the current selected state
*/
public void setSelectedState(boolean state) {
- currentValue = Boolean.toString(state);
+ mCurrentValue = Boolean.toString(state);
}
/**
@@ -199,7 +232,7 @@ public class RestrictionEntry implements Parcelable {
* @param allSelectedStrings the current list of selected values.
*/
public void setAllSelectedStrings(String[] allSelectedStrings) {
- currentValues = allSelectedStrings;
+ mCurrentValues = allSelectedStrings;
}
/**
@@ -216,7 +249,7 @@ public class RestrictionEntry implements Parcelable {
* @see #getAllSelectedStrings()
*/
public void setChoiceValues(String[] choiceValues) {
- values = choiceValues;
+ mChoiceValues = choiceValues;
}
/**
@@ -227,7 +260,7 @@ public class RestrictionEntry implements Parcelable {
* @see #setChoiceValues(String[])
*/
public void setChoiceValues(Context context, int stringArrayResId) {
- values = context.getResources().getStringArray(stringArrayResId);
+ mChoiceValues = context.getResources().getStringArray(stringArrayResId);
}
/**
@@ -235,7 +268,7 @@ public class RestrictionEntry implements Parcelable {
* @return the list of possible values.
*/
public String[] getChoiceValues() {
- return values;
+ return mChoiceValues;
}
/**
@@ -248,7 +281,7 @@ public class RestrictionEntry implements Parcelable {
* @see #setChoiceValues(String[])
*/
public void setChoiceEntries(String[] choiceEntries) {
- choices = choiceEntries;
+ mChoiceEntries = choiceEntries;
}
/** Sets a list of strings that will be presented as choices to the user. This is similar to
@@ -257,7 +290,7 @@ public class RestrictionEntry implements Parcelable {
* @param stringArrayResId the resource id of a string array containing the possible entries.
*/
public void setChoiceEntries(Context context, int stringArrayResId) {
- choices = context.getResources().getStringArray(stringArrayResId);
+ mChoiceEntries = context.getResources().getStringArray(stringArrayResId);
}
/**
@@ -265,7 +298,7 @@ public class RestrictionEntry implements Parcelable {
* @return the list of choices presented to the user.
*/
public String[] getChoiceEntries() {
- return choices;
+ return mChoiceEntries;
}
/**
@@ -273,7 +306,7 @@ public class RestrictionEntry implements Parcelable {
* @return the user-visible description, null if none was set earlier.
*/
public String getDescription() {
- return description;
+ return mDescription;
}
/**
@@ -283,7 +316,7 @@ public class RestrictionEntry implements Parcelable {
* @param description the user-visible description string.
*/
public void setDescription(String description) {
- this.description = description;
+ this.mDescription = description;
}
/**
@@ -291,7 +324,7 @@ public class RestrictionEntry implements Parcelable {
* @return the key for the restriction.
*/
public String getKey() {
- return key;
+ return mKey;
}
/**
@@ -299,7 +332,7 @@ public class RestrictionEntry implements Parcelable {
* @return the user-visible title for the entry, null if none was set earlier.
*/
public String getTitle() {
- return title;
+ return mTitle;
}
/**
@@ -307,7 +340,7 @@ public class RestrictionEntry implements Parcelable {
* @param title the user-visible title for the entry.
*/
public void setTitle(String title) {
- this.title = title;
+ this.mTitle = title;
}
private boolean equalArrays(String[] one, String[] other) {
@@ -324,23 +357,23 @@ public class RestrictionEntry implements Parcelable {
if (!(o instanceof RestrictionEntry)) return false;
final RestrictionEntry other = (RestrictionEntry) o;
// Make sure that either currentValue matches or currentValues matches.
- return type == other.type && key.equals(other.key)
+ return mType == other.mType && mKey.equals(other.mKey)
&&
- ((currentValues == null && other.currentValues == null
- && currentValue != null && currentValue.equals(other.currentValue))
+ ((mCurrentValues == null && other.mCurrentValues == null
+ && mCurrentValue != null && mCurrentValue.equals(other.mCurrentValue))
||
- (currentValue == null && other.currentValue == null
- && currentValues != null && equalArrays(currentValues, other.currentValues)));
+ (mCurrentValue == null && other.mCurrentValue == null
+ && mCurrentValues != null && equalArrays(mCurrentValues, other.mCurrentValues)));
}
@Override
public int hashCode() {
int result = 17;
- result = 31 * result + key.hashCode();
- if (currentValue != null) {
- result = 31 * result + currentValue.hashCode();
- } else if (currentValues != null) {
- for (String value : currentValues) {
+ result = 31 * result + mKey.hashCode();
+ if (mCurrentValue != null) {
+ result = 31 * result + mCurrentValue.hashCode();
+ } else if (mCurrentValues != null) {
+ for (String value : mCurrentValues) {
if (value != null) {
result = 31 * result + value.hashCode();
}
@@ -359,14 +392,14 @@ public class RestrictionEntry implements Parcelable {
}
public RestrictionEntry(Parcel in) {
- type = in.readInt();
- key = in.readString();
- title = in.readString();
- description = in.readString();
- choices = readArray(in);
- values = readArray(in);
- currentValue = in.readString();
- currentValues = readArray(in);
+ mType = in.readInt();
+ mKey = in.readString();
+ mTitle = in.readString();
+ mDescription = in.readString();
+ mChoiceEntries = readArray(in);
+ mChoiceValues = readArray(in);
+ mCurrentValue = in.readString();
+ mCurrentValues = readArray(in);
}
@Override
@@ -387,14 +420,14 @@ public class RestrictionEntry implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(type);
- dest.writeString(key);
- dest.writeString(title);
- dest.writeString(description);
- writeArray(dest, choices);
- writeArray(dest, values);
- dest.writeString(currentValue);
- writeArray(dest, currentValues);
+ dest.writeInt(mType);
+ dest.writeString(mKey);
+ dest.writeString(mTitle);
+ dest.writeString(mDescription);
+ writeArray(dest, mChoiceEntries);
+ writeArray(dest, mChoiceValues);
+ dest.writeString(mCurrentValue);
+ writeArray(dest, mCurrentValues);
}
public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() {
@@ -409,6 +442,6 @@ public class RestrictionEntry implements Parcelable {
@Override
public String toString() {
- return "RestrictionsEntry {type=" + type + ", key=" + key + ", value=" + currentValue + "}";
+ return "RestrictionsEntry {type=" + mType + ", key=" + mKey + ", value=" + mCurrentValue + "}";
}
}
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
new file mode 100644
index 0000000..0dd0edd
--- /dev/null
+++ b/core/java/android/content/RestrictionsManager.java
@@ -0,0 +1,344 @@
+/*
+ * 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.admin.DevicePolicyManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides a mechanism for apps to query restrictions imposed by an entity that
+ * manages the user. Apps can also send permission requests to a local or remote
+ * device administrator to override default app-specific restrictions or any other
+ * operation that needs explicit authorization from the administrator.
+ * <p>
+ * Apps can expose a set of restrictions via a runtime receiver mechanism or via
+ * static meta data in the manifest.
+ * <p>
+ * If the user has an active restrictions provider, dynamic requests can be made in
+ * addition to the statically imposed restrictions. Dynamic requests are app-specific
+ * and can be expressed via a predefined set of templates.
+ * <p>
+ * The RestrictionsManager forwards the dynamic requests to the active
+ * restrictions provider. The restrictions provider can respond back to requests by calling
+ * {@link #notifyPermissionResponse(String, Bundle)}, when
+ * a response is received from the administrator of the device or user
+ * The response is relayed back to the application via a protected broadcast,
+ * {@link #ACTION_PERMISSION_RESPONSE_RECEIVED}.
+ * <p>
+ * Static restrictions are specified by an XML file referenced by a meta-data attribute
+ * in the manifest. This enables applications as well as any web administration consoles
+ * to be able to read the template from the apk.
+ * <p>
+ * The syntax of the XML format is as follows:
+ * <pre>
+ * &lt;restrictions&gt;
+ * &lt;restriction
+ * android:key="&lt;key&gt;"
+ * android:restrictionType="boolean|string|integer|multi-select|null"
+ * ... /&gt;
+ * &lt;restriction ... /&gt;
+ * &lt;/restrictions&gt;
+ * </pre>
+ * <p>
+ * The attributes for each restriction depend on the restriction type.
+ *
+ * @see RestrictionEntry
+ */
+public class RestrictionsManager {
+
+ /**
+ * Broadcast intent delivered when a response is received for a permission
+ * request. The response is not available for later query, so the receiver
+ * must persist and/or immediately act upon the response. The application
+ * should not interrupt the user by coming to the foreground if it isn't
+ * currently in the foreground. It can post a notification instead, informing
+ * the user of a change in state.
+ * <p>
+ * For instance, if the user requested permission to make an in-app purchase,
+ * the app can post a notification that the request had been granted or denied,
+ * and allow the purchase to go through.
+ * <p>
+ * The broadcast Intent carries the following extra:
+ * {@link #EXTRA_RESPONSE_BUNDLE}.
+ */
+ public static final String ACTION_PERMISSION_RESPONSE_RECEIVED =
+ "android.intent.action.PERMISSION_RESPONSE_RECEIVED";
+
+ /**
+ * Protected broadcast intent sent to the active restrictions provider. The intent
+ * contains the following extras:<p>
+ * <ul>
+ * <li>{@link #EXTRA_PACKAGE_NAME} : String; the package name of the application requesting
+ * permission.</li>
+ * <li>{@link #EXTRA_TEMPLATE_ID} : String; the template of the request.</li>
+ * <li>{@link #EXTRA_REQUEST_BUNDLE} : Bundle; contains the template-specific keys and values
+ * for the request.
+ * </ul>
+ * @see DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName)
+ * @see #requestPermission(String, String, Bundle)
+ */
+ public static final String ACTION_REQUEST_PERMISSION =
+ "android.intent.action.REQUEST_PERMISSION";
+
+ /**
+ * The package name of the application making the request.
+ */
+ public static final String EXTRA_PACKAGE_NAME = "package_name";
+
+ /**
+ * The template id that specifies what kind of a request it is and may indicate
+ * how the request is to be presented to the administrator. Must be either one of
+ * the predefined templates or a custom one specified by the application that the
+ * restrictions provider is familiar with.
+ */
+ public static final String EXTRA_TEMPLATE_ID = "template_id";
+
+ /**
+ * A bundle containing the details about the request. The contents depend on the
+ * template id.
+ * @see #EXTRA_TEMPLATE_ID
+ */
+ public static final String EXTRA_REQUEST_BUNDLE = "request_bundle";
+
+ /**
+ * Contains a response from the administrator for specific request.
+ * The bundle contains the following information, at least:
+ * <ul>
+ * <li>{@link #REQUEST_KEY_ID}: The request id.</li>
+ * <li>{@link #REQUEST_KEY_DATA}: The request reference data.</li>
+ * </ul>
+ * <p>
+ * And depending on what the request template was, the bundle will contain the actual
+ * result of the request. For {@link #REQUEST_TEMPLATE_QUESTION}, the result will be in
+ * {@link #RESPONSE_KEY_BOOLEAN}, which is of type boolean; true if the administrator
+ * approved the request, false otherwise.
+ */
+ public static final String EXTRA_RESPONSE_BUNDLE = "response_bundle";
+
+
+ /**
+ * Request template that presents a simple question, with a possible title and icon.
+ * <p>
+ * Required keys are
+ * {@link #REQUEST_KEY_ID} and {@link #REQUEST_KEY_MESSAGE}.
+ * <p>
+ * Optional keys are
+ * {@link #REQUEST_KEY_DATA}, {@link #REQUEST_KEY_ICON}, {@link #REQUEST_KEY_TITLE},
+ * {@link #REQUEST_KEY_APPROVE_LABEL} and {@link #REQUEST_KEY_DENY_LABEL}.
+ */
+ public static final String REQUEST_TEMPLATE_QUESTION = "android.req_template.type.simple";
+
+ /**
+ * Key for request ID contained in the request bundle.
+ * <p>
+ * App-generated request id to identify the specific request when receiving
+ * a response. This value is returned in the {@link #EXTRA_RESPONSE_BUNDLE}.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_ID = "android.req_template.req_id";
+
+ /**
+ * Key for request data contained in the request bundle.
+ * <p>
+ * Optional, typically used to identify the specific data that is being referred to,
+ * such as the unique identifier for a movie or book. This is not used for display
+ * purposes and is more like a cookie. This value is returned in the
+ * {@link #EXTRA_RESPONSE_BUNDLE}.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_DATA = "android.req_template.data";
+
+ /**
+ * Key for request title contained in the request bundle.
+ * <p>
+ * Optional, typically used as the title of any notification or dialog presented
+ * to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_TITLE = "android.req_template.title";
+
+ /**
+ * Key for request message contained in the request bundle.
+ * <p>
+ * Required, shown as the actual message in a notification or dialog presented
+ * to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_MESSAGE = "android.req_template.mesg";
+
+ /**
+ * Key for request icon contained in the request bundle.
+ * <p>
+ * Optional, shown alongside the request message presented to the administrator
+ * who approves the request.
+ * <p>
+ * Type: Bitmap
+ */
+ public static final String REQUEST_KEY_ICON = "android.req_template.icon";
+
+ /**
+ * Key for request approval button label contained in the request bundle.
+ * <p>
+ * Optional, may be shown as a label on the positive button in a dialog or
+ * notification presented to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_APPROVE_LABEL = "android.req_template.accept";
+
+ /**
+ * Key for request rejection button label contained in the request bundle.
+ * <p>
+ * Optional, may be shown as a label on the negative button in a dialog or
+ * notification presented to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_DENY_LABEL = "android.req_template.reject";
+
+ /**
+ * Key for requestor's name contained in the request bundle. This value is not specified by
+ * the application. It is automatically inserted into the Bundle by the Restrictions Provider
+ * before it is sent to the administrator.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_REQUESTOR_NAME = "android.req_template.requestor";
+
+ /**
+ * Key for requestor's device name contained in the request bundle. This value is not specified
+ * by the application. It is automatically inserted into the Bundle by the Restrictions Provider
+ * before it is sent to the administrator.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_DEVICE_NAME = "android.req_template.device";
+
+ /**
+ * Key for the response in the response bundle sent to the application, for a permission
+ * request.
+ * <p>
+ * Type: boolean
+ */
+ public static final String RESPONSE_KEY_BOOLEAN = "android.req_template.response";
+
+ private static final String TAG = "RestrictionsManager";
+
+ private final Context mContext;
+ private final IRestrictionsManager mService;
+
+ /**
+ * @hide
+ */
+ public RestrictionsManager(Context context, IRestrictionsManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Returns any available set of application-specific restrictions applicable
+ * to this application.
+ * @return
+ */
+ public Bundle getApplicationRestrictions() {
+ try {
+ if (mService != null) {
+ return mService.getApplicationRestrictions(mContext.getPackageName());
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Couldn't reach service");
+ }
+ return null;
+ }
+
+ /**
+ * Called by an application to check if permission requests can be made. If false,
+ * there is no need to request permission for an operation, unless a static
+ * restriction applies to that operation.
+ * @return
+ */
+ public boolean hasRestrictionsProvider() {
+ try {
+ if (mService != null) {
+ return mService.hasRestrictionsProvider();
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Couldn't reach service");
+ }
+ return false;
+ }
+
+ /**
+ * Called by an application to request permission for an operation. The contents of the
+ * request are passed in a Bundle that contains several pieces of data depending on the
+ * chosen request template.
+ *
+ * @param requestTemplate The request template to use. The template could be one of the
+ * predefined templates specified in this class or a custom template that the specific
+ * Restrictions Provider might understand. For custom templates, the template name should be
+ * namespaced to avoid collisions with predefined templates and templates specified by
+ * other Restrictions Provider vendors.
+ * @param requestData A Bundle containing the data corresponding to the specified request
+ * template. The keys for the data in the bundle depend on the kind of template chosen.
+ */
+ public void requestPermission(String requestTemplate, Bundle requestData) {
+ try {
+ if (mService != null) {
+ mService.requestPermission(mContext.getPackageName(), requestTemplate, requestData);
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Couldn't reach service");
+ }
+ }
+
+ /**
+ * Called by the Restrictions Provider when a response is available to be
+ * delivered to an application.
+ * @param packageName
+ * @param response
+ */
+ public void notifyPermissionResponse(String packageName, Bundle response) {
+ try {
+ if (mService != null) {
+ mService.notifyPermissionResponse(packageName, response);
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Couldn't reach service");
+ }
+ }
+
+ /**
+ * Parse and return the list of restrictions defined in the manifest for the specified
+ * package, if any.
+ * @param packageName The application for which to fetch the restrictions list.
+ * @return The list of RestrictionEntry objects created from the XML file specified
+ * in the manifest, or null if none was specified.
+ */
+ public List<RestrictionEntry> getManifestRestrictions(String packageName) {
+ // TODO:
+ return null;
+ }
+}
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index d4f7f06..46c9234 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -82,7 +82,7 @@ public interface SharedPreferences {
/**
* Set a set of String values in the preferences editor, to be written
- * back once {@link #commit} is called.
+ * back once {@link #commit} or {@link #apply} is called.
*
* @param key The name of the preference to modify.
* @param values The set of new values for the preference. Passing {@code null}
@@ -355,7 +355,14 @@ public interface SharedPreferences {
/**
* Registers a callback to be invoked when a change happens to a preference.
- *
+ *
+ * <p class="caution"><strong>Caution:</strong> The preference manager does
+ * not currently store a strong reference to the listener. You must store a
+ * strong reference to the listener, or it will be susceptible to garbage
+ * collection. We recommend you keep a reference to the listener in the
+ * instance data of an object that will exist as long as you need the
+ * listener.</p>
+ *
* @param listener The callback that will run.
* @see #unregisterOnSharedPreferenceChangeListener
*/
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 0d1b262..6b44a11 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -453,7 +453,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*
* {@hide}
*/
- public String requiredCpuAbi;
+ public String cpuAbi;
/**
* The kernel user-ID that has been assigned to this application;
@@ -592,7 +592,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
sourceDir = orig.sourceDir;
publicSourceDir = orig.publicSourceDir;
nativeLibraryDir = orig.nativeLibraryDir;
- requiredCpuAbi = orig.requiredCpuAbi;
+ cpuAbi = orig.cpuAbi;
resourceDirs = orig.resourceDirs;
seinfo = orig.seinfo;
sharedLibraryFiles = orig.sharedLibraryFiles;
@@ -634,7 +634,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeString(sourceDir);
dest.writeString(publicSourceDir);
dest.writeString(nativeLibraryDir);
- dest.writeString(requiredCpuAbi);
+ dest.writeString(cpuAbi);
dest.writeStringArray(resourceDirs);
dest.writeString(seinfo);
dest.writeStringArray(sharedLibraryFiles);
@@ -675,7 +675,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
sourceDir = source.readString();
publicSourceDir = source.readString();
nativeLibraryDir = source.readString();
- requiredCpuAbi = source.readString();
+ cpuAbi = source.readString();
resourceDirs = source.readStringArray();
seinfo = source.readString();
sharedLibraryFiles = source.readStringArray();
diff --git a/core/java/android/content/pm/ContainerEncryptionParams.java b/core/java/android/content/pm/ContainerEncryptionParams.java
index 88112a7..dd1332b 100644
--- a/core/java/android/content/pm/ContainerEncryptionParams.java
+++ b/core/java/android/content/pm/ContainerEncryptionParams.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.annotation.PrivateApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -31,8 +32,11 @@ import javax.crypto.spec.IvParameterSpec;
/**
* Represents encryption parameters used to read a container.
*
+ * @deprecated encrypted containers are legacy.
* @hide
*/
+@PrivateApi
+@Deprecated
public class ContainerEncryptionParams implements Parcelable {
protected static final String TAG = "ContainerEncryptionParams";
diff --git a/core/java/android/content/pm/IPackageInstallObserver2.aidl b/core/java/android/content/pm/IPackageInstallObserver2.aidl
index 2602ab5..7205ce7 100644
--- a/core/java/android/content/pm/IPackageInstallObserver2.aidl
+++ b/core/java/android/content/pm/IPackageInstallObserver2.aidl
@@ -1,22 +1,22 @@
/*
-**
-** 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.
-*/
+ * 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.IntentSender;
import android.os.Bundle;
/**
@@ -40,6 +40,5 @@ oneway interface IPackageInstallObserver2 {
* </tr>
* </table>
*/
- void packageInstalled(in String packageName, in Bundle extras, int returnCode);
+ void packageInstalled(String basePackageName, in Bundle extras, int returnCode);
}
-
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
new file mode 100644
index 0000000..68c019b
--- /dev/null
+++ b/core/java/android/content/pm/IPackageInstaller.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 android.content.pm;
+
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageInstallerSession;
+import android.content.pm.PackageInstallerParams;
+import android.os.ParcelFileDescriptor;
+
+/** {@hide} */
+interface IPackageInstaller {
+ int createSession(int userId, String installerPackageName, in PackageInstallerParams params);
+ IPackageInstallerSession openSession(int sessionId);
+
+ int[] getSessions(int userId, String installerPackageName);
+
+ void uninstall(int userId, String basePackageName, in IPackageDeleteObserver observer);
+ void uninstallSplit(int userId, String basePackageName, String splitName, in IPackageDeleteObserver observer);
+}
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
new file mode 100644
index 0000000..f881acd
--- /dev/null
+++ b/core/java/android/content/pm/IPackageInstallerSession.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.content.pm.IPackageInstallObserver2;
+import android.os.ParcelFileDescriptor;
+
+/** {@hide} */
+interface IPackageInstallerSession {
+ void updateProgress(int progress);
+
+ ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes);
+
+ void install(in IPackageInstallObserver2 observer);
+ void destroy();
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 03eb50f..70668e1 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -26,6 +26,7 @@ import android.content.pm.ContainerEncryptionParams;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageInstallObserver2;
+import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageMoveObserver;
@@ -111,7 +112,7 @@ interface IPackageManager {
ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags, int userId);
- boolean canForwardTo(in Intent intent, String resolvedType, int userIdFrom, int userIdDest);
+ boolean canForwardTo(in Intent intent, String resolvedType, int sourceUserId, int targetUserId);
List<ResolveInfo> queryIntentActivities(in Intent intent,
String resolvedType, int flags, int userId);
@@ -247,10 +248,10 @@ interface IPackageManager {
void clearPackagePersistentPreferredActivities(String packageName, int userId);
- void addForwardingIntentFilter(in IntentFilter filter, boolean removable, int userIdOrig,
- int userIdDest);
+ void addCrossProfileIntentFilter(in IntentFilter filter, boolean removable, int sourceUserId,
+ int targetUserId);
- void clearForwardingIntentFilters(int userIdOrig);
+ void clearCrossProfileIntentFilters(int sourceUserId);
/**
* Report the set of 'Home' activity candidates, plus (if any) which of them
@@ -432,6 +433,13 @@ interface IPackageManager {
in VerificationParams verificationParams,
in ContainerEncryptionParams encryptionParams);
+ void installPackageWithVerificationEncryptionAndAbiOverrideEtc(in Uri packageURI,
+ in IPackageInstallObserver observer, in IPackageInstallObserver2 observer2,
+ int flags, in String installerPackageName,
+ in VerificationParams verificationParams,
+ in ContainerEncryptionParams encryptionParams,
+ in String packageAbiOverride);
+
int installExistingPackageAsUser(String packageName, int userId);
void verifyPendingInstall(int id, int verificationCode);
@@ -450,4 +458,6 @@ interface IPackageManager {
boolean setApplicationBlockedSettingAsUser(String packageName, boolean blocked, int userId);
boolean getApplicationBlockedSettingAsUser(String packageName, int userId);
+
+ IPackageInstaller getPackageInstaller();
}
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index 9087338..5d48868 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -30,6 +30,7 @@ import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.DisplayMetrics;
import android.util.Log;
/**
@@ -47,21 +48,22 @@ public class LauncherActivityInfo {
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) {
+ LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user,
+ long firstInstallTime) {
this(context);
- this.mActivityInfo = info.activityInfo;
- this.mComponentName = LauncherApps.getComponentName(info);
- this.mUser = user;
+ mActivityInfo = info.activityInfo;
+ mComponentName = LauncherApps.getComponentName(info);
+ mUser = user;
+ mFirstInstallTime = firstInstallTime;
}
LauncherActivityInfo(Context context) {
@@ -79,7 +81,13 @@ public class LauncherActivityInfo {
}
/**
- * Returns the user handle of the user profile that this activity belongs to.
+ * Returns the user handle of the user profile that this activity belongs to. In order to
+ * persist the identity of the profile, do not store the UserHandle. Instead retrieve its
+ * serial number from UserManager. You can convert the serial number back to a UserHandle
+ * for later use.
+ *
+ * @see UserManager#getSerialNumberForUser(UserHandle)
+ * @see UserManager#getUserForSerialNumber(long)
*
* @return The UserHandle of the profile.
*/
@@ -89,7 +97,7 @@ public class LauncherActivityInfo {
/**
* Retrieves the label for the activity.
- *
+ *
* @return The label for the activity.
*/
public CharSequence getLabel() {
@@ -98,8 +106,10 @@ public class LauncherActivityInfo {
/**
* Returns the icon for this activity, without any badging for the profile.
- * @param density The preferred density of the icon, zero for default density.
+ * @param density The preferred density of the icon, zero for default density. Use
+ * density DPI values from {@link DisplayMetrics}.
* @see #getBadgedIcon(int)
+ * @see DisplayMetrics
* @return The drawable associated with the activity
*/
public Drawable getIcon(int density) {
@@ -109,15 +119,25 @@ public class LauncherActivityInfo {
/**
* Returns the application flags from the ApplicationInfo of the activity.
- *
+ *
* @return Application flags
+ * @hide remove before shipping
*/
public int getApplicationFlags() {
return mActivityInfo.applicationInfo.flags;
}
/**
+ * Returns the application info for the appliction this activity belongs to.
+ * @return
+ */
+ public ApplicationInfo getApplicationInfo() {
+ return mActivityInfo.applicationInfo;
+ }
+
+ /**
* Returns the time at which the package was first installed.
+ *
* @return The time of installation of the package, in milliseconds.
*/
public long getFirstInstallTime() {
@@ -134,7 +154,9 @@ public class LauncherActivityInfo {
/**
* Returns the activity icon with badging appropriate for the profile.
- * @param density Optional density for the icon, or 0 to use the default density.
+ * @param density Optional density for the icon, or 0 to use the default density. Use
+ * {@link DisplayMetrics} for DPI values.
+ * @see DisplayMetrics
* @return A badged icon for the activity.
*/
public Drawable getBadgedIcon(int density) {
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 8025b60..04c0b9f 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -16,15 +16,18 @@
package android.content.pm;
+import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ILauncherApps;
import android.content.pm.IOnAppsChangedListener;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import java.util.ArrayList;
@@ -36,6 +39,12 @@ import java.util.List;
* 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.
+ * <p>
+ * To watch for managed profiles being added or removed, register for the following broadcasts:
+ * {@link Intent#ACTION_MANAGED_PROFILE_ADDED} and {@link Intent#ACTION_MANAGED_PROFILE_REMOVED}.
+ * <p>
+ * You can retrieve the list of profiles associated with this user with
+ * {@link UserManager#getUserProfiles()}.
*/
public class LauncherApps {
@@ -44,12 +53,13 @@ public class LauncherApps {
private Context mContext;
private ILauncherApps mService;
+ private PackageManager mPm;
private List<OnAppsChangedListener> mListeners
= new ArrayList<OnAppsChangedListener>();
/**
- * Callbacks for changes to this and related managed profiles.
+ * Callbacks for package changes to this and related managed profiles.
*/
public interface OnAppsChangedListener {
/**
@@ -57,6 +67,7 @@ public class LauncherApps {
*
* @param user The UserHandle of the profile that generated the change.
* @param packageName The name of the package that was removed.
+ * @hide remove before ship
*/
void onPackageRemoved(UserHandle user, String packageName);
@@ -65,6 +76,7 @@ public class LauncherApps {
*
* @param user The UserHandle of the profile that generated the change.
* @param packageName The name of the package that was added.
+ * @hide remove before ship
*/
void onPackageAdded(UserHandle user, String packageName);
@@ -73,6 +85,7 @@ public class LauncherApps {
*
* @param user The UserHandle of the profile that generated the change.
* @param packageName The name of the package that has changed.
+ * @hide remove before ship
*/
void onPackageChanged(UserHandle user, String packageName);
@@ -86,6 +99,7 @@ public class LauncherApps {
* available.
* @param replacing Indicates whether these packages are replacing
* existing ones.
+ * @hide remove before ship
*/
void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing);
@@ -99,14 +113,66 @@ public class LauncherApps {
* unavailable.
* @param replacing Indicates whether the packages are about to be
* replaced with new versions.
+ * @hide remove before ship
*/
void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing);
+
+ /**
+ * Indicates that a package was removed from the specified profile.
+ *
+ * @param packageName The name of the package that was removed.
+ * @param user The UserHandle of the profile that generated the change.
+ */
+ void onPackageRemoved(String packageName, UserHandle user);
+
+ /**
+ * Indicates that a package was added to the specified profile.
+ *
+ * @param packageName The name of the package that was added.
+ * @param user The UserHandle of the profile that generated the change.
+ */
+ void onPackageAdded(String packageName, UserHandle user);
+
+ /**
+ * Indicates that a package was modified in the specified profile.
+ *
+ * @param packageName The name of the package that has changed.
+ * @param user The UserHandle of the profile that generated the change.
+ */
+ void onPackageChanged(String packageName, UserHandle user);
+
+ /**
+ * Indicates that one or more packages have become available. For
+ * example, this can happen when a removable storage card has
+ * reappeared.
+ *
+ * @param packageNames The names of the packages that have become
+ * available.
+ * @param user The UserHandle of the profile that generated the change.
+ * @param replacing Indicates whether these packages are replacing
+ * existing ones.
+ */
+ void onPackagesAvailable(String [] packageNames, UserHandle user, 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 packageNames The names of the packages that have become
+ * unavailable.
+ * @param user The UserHandle of the profile that generated the change.
+ * @param replacing Indicates whether the packages are about to be
+ * replaced with new versions.
+ */
+ void onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing);
}
/** @hide */
public LauncherApps(Context context, ILauncherApps service) {
mContext = context;
mService = service;
+ mPm = context.getPackageManager();
}
/**
@@ -131,7 +197,15 @@ public class LauncherApps {
final int count = activities.size();
for (int i = 0; i < count; i++) {
ResolveInfo ri = activities.get(i);
- LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri, user);
+ long firstInstallTime = 0;
+ try {
+ firstInstallTime = mPm.getPackageInfo(ri.activityInfo.packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES).firstInstallTime;
+ } catch (NameNotFoundException nnfe) {
+ // Sorry, can't find package
+ }
+ LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri, user,
+ firstInstallTime);
if (DEBUG) {
Log.v(TAG, "Returning activity for profile " + user + " : "
+ lai.getComponentName());
@@ -157,7 +231,15 @@ public class LauncherApps {
try {
ResolveInfo ri = mService.resolveActivity(intent, user);
if (ri != null) {
- LauncherActivityInfo info = new LauncherActivityInfo(mContext, ri, user);
+ long firstInstallTime = 0;
+ try {
+ firstInstallTime = mPm.getPackageInfo(ri.activityInfo.packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES).firstInstallTime;
+ } catch (NameNotFoundException nnfe) {
+ // Sorry, can't find package
+ }
+ LauncherActivityInfo info = new LauncherActivityInfo(mContext, ri, user,
+ firstInstallTime);
return info;
}
} catch (RemoteException re) {
@@ -173,9 +255,23 @@ public class LauncherApps {
* @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
+ * @hide remove before ship
*/
public void startActivityForProfile(ComponentName component, Rect sourceBounds,
Bundle opts, UserHandle user) {
+ startActivityForProfile(component, user, sourceBounds, opts);
+ }
+
+ /**
+ * Starts an activity in the specified profile.
+ *
+ * @param component The ComponentName of the activity to launch
+ * @param user The UserHandle of the profile
+ * @param sourceBounds The Rect containing the source bounds of the clicked icon
+ * @param opts Options to pass to startActivity
+ */
+ public void startActivityForProfile(ComponentName component, UserHandle user, Rect sourceBounds,
+ Bundle opts) {
if (DEBUG) {
Log.i(TAG, "StartActivityForProfile " + component + " " + user.getIdentifier());
}
@@ -224,13 +320,15 @@ public class LauncherApps {
*
* @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) {
+ public void addOnAppsChangedListener(OnAppsChangedListener listener) {
+ synchronized (this) {
+ if (listener != null && !mListeners.contains(listener)) {
+ mListeners.add(listener);
+ if (mListeners.size() == 1) {
+ try {
+ mService.addOnAppsChangedListener(mAppsChangedListener);
+ } catch (RemoteException re) {
+ }
}
}
}
@@ -242,12 +340,14 @@ public class LauncherApps {
* @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) {
+ public void removeOnAppsChangedListener(OnAppsChangedListener listener) {
+ synchronized (this) {
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ try {
+ mService.removeOnAppsChangedListener(mAppsChangedListener);
+ } catch (RemoteException re) {
+ }
}
}
}
@@ -261,7 +361,8 @@ public class LauncherApps {
}
synchronized (LauncherApps.this) {
for (OnAppsChangedListener listener : mListeners) {
- listener.onPackageRemoved(user, packageName);
+ listener.onPackageRemoved(user, packageName); // TODO: Remove before ship
+ listener.onPackageRemoved(packageName, user);
}
}
}
@@ -273,7 +374,8 @@ public class LauncherApps {
}
synchronized (LauncherApps.this) {
for (OnAppsChangedListener listener : mListeners) {
- listener.onPackageChanged(user, packageName);
+ listener.onPackageChanged(user, packageName); // TODO: Remove before ship
+ listener.onPackageChanged(packageName, user);
}
}
}
@@ -285,7 +387,8 @@ public class LauncherApps {
}
synchronized (LauncherApps.this) {
for (OnAppsChangedListener listener : mListeners) {
- listener.onPackageAdded(user, packageName);
+ listener.onPackageAdded(user, packageName); // TODO: Remove before ship
+ listener.onPackageAdded(packageName, user);
}
}
}
@@ -298,7 +401,8 @@ public class LauncherApps {
}
synchronized (LauncherApps.this) {
for (OnAppsChangedListener listener : mListeners) {
- listener.onPackagesAvailable(user, packageNames, replacing);
+ listener.onPackagesAvailable(user, packageNames, replacing); // TODO: Remove
+ listener.onPackagesAvailable(packageNames, user, replacing);
}
}
}
@@ -311,7 +415,8 @@ public class LauncherApps {
}
synchronized (LauncherApps.this) {
for (OnAppsChangedListener listener : mListeners) {
- listener.onPackagesUnavailable(user, packageNames, replacing);
+ listener.onPackagesUnavailable(user, packageNames, replacing); // TODO: Remove
+ listener.onPackagesUnavailable(packageNames, user, replacing);
}
}
}
diff --git a/core/java/android/content/pm/ManifestDigest.java b/core/java/android/content/pm/ManifestDigest.java
index 409b5ae..943534f 100644
--- a/core/java/android/content/pm/ManifestDigest.java
+++ b/core/java/android/content/pm/ManifestDigest.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.annotation.PrivateApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Slog;
@@ -36,6 +37,7 @@ import libcore.io.IoUtils;
*
* @hide
*/
+@PrivateApi
public class ManifestDigest implements Parcelable {
private static final String TAG = "ManifestDigest";
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
new file mode 100644
index 0000000..4672015
--- /dev/null
+++ b/core/java/android/content/pm/PackageInstaller.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.content.pm;
+
+import android.app.PackageInstallObserver;
+import android.app.PackageUninstallObserver;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.FileBridge;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import java.io.OutputStream;
+
+/** {@hide} */
+public class PackageInstaller {
+ private final PackageManager mPm;
+ private final IPackageInstaller mInstaller;
+ private final int mUserId;
+ private final String mInstallerPackageName;
+
+ /** {@hide} */
+ public PackageInstaller(PackageManager pm, IPackageInstaller installer, int userId,
+ String installerPackageName) {
+ mPm = pm;
+ mInstaller = installer;
+ mUserId = userId;
+ mInstallerPackageName = installerPackageName;
+ }
+
+ public boolean isPackageAvailable(String basePackageName) {
+ try {
+ final ApplicationInfo info = mPm.getApplicationInfo(basePackageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES);
+ return ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ public void installAvailablePackage(String basePackageName, PackageInstallObserver observer) {
+ int returnCode;
+ try {
+ returnCode = mPm.installExistingPackage(basePackageName);
+ } catch (NameNotFoundException e) {
+ returnCode = PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
+ }
+ observer.packageInstalled(basePackageName, null, returnCode);
+ }
+
+ public int createSession(PackageInstallerParams params) {
+ try {
+ return mInstaller.createSession(mUserId, mInstallerPackageName, params);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public Session openSession(int sessionId) {
+ try {
+ return new Session(mInstaller.openSession(sessionId));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public int[] getSessions() {
+ try {
+ return mInstaller.getSessions(mUserId, mInstallerPackageName);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public void uninstall(String basePackageName, PackageUninstallObserver observer) {
+ try {
+ mInstaller.uninstall(mUserId, basePackageName, observer.getBinder());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public void uninstall(String basePackageName, String splitName,
+ PackageUninstallObserver observer) {
+ try {
+ mInstaller.uninstallSplit(mUserId, basePackageName, splitName, observer.getBinder());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * An installation that is being actively staged. For an install to succeed,
+ * all existing and new packages must have identical package names, version
+ * codes, and signing certificates.
+ * <p>
+ * A session may contain any number of split packages. If the application
+ * does not yet exist, this session must include a base package.
+ * <p>
+ * If a package included in this session is already defined by the existing
+ * installation (for example, the same split name), the package in this
+ * session will replace the existing package.
+ */
+ public class Session {
+ private IPackageInstallerSession mSession;
+
+ /** {@hide} */
+ public Session(IPackageInstallerSession session) {
+ mSession = session;
+ }
+
+ public void updateProgress(int progress) {
+ try {
+ mSession.updateProgress(progress);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Open an APK file for writing, starting at the given offset. You can
+ * then stream data into the file, periodically calling
+ * {@link OutputStream#flush()} to ensure bytes have been written to
+ * disk.
+ */
+ public OutputStream openWrite(String splitName, long offsetBytes, long lengthBytes) {
+ try {
+ final ParcelFileDescriptor clientSocket = mSession.openWrite(splitName,
+ offsetBytes, lengthBytes);
+ return new FileBridge.FileBridgeOutputStream(clientSocket.getFileDescriptor());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public void install(PackageInstallObserver observer) {
+ try {
+ mSession.install(observer.getBinder());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public void close() {
+ // No resources to release at the moment
+ }
+
+ public void destroy() {
+ try {
+ mSession.destroy();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+ }
+}
diff --git a/core/java/android/content/pm/PackageInstallerParams.aidl b/core/java/android/content/pm/PackageInstallerParams.aidl
new file mode 100644
index 0000000..b3dde21
--- /dev/null
+++ b/core/java/android/content/pm/PackageInstallerParams.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.content.pm;
+
+parcelable PackageInstallerParams;
diff --git a/core/java/android/content/pm/PackageInstallerParams.java b/core/java/android/content/pm/PackageInstallerParams.java
new file mode 100644
index 0000000..67cf276
--- /dev/null
+++ b/core/java/android/content/pm/PackageInstallerParams.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.content.pm;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Parameters that define an installation session.
+ *
+ * {@hide}
+ */
+public class PackageInstallerParams implements Parcelable {
+
+ // TODO: extend to support remaining VerificationParams
+
+ /** {@hide} */
+ public boolean fullInstall;
+ /** {@hide} */
+ public int installFlags;
+ /** {@hide} */
+ public int installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+ /** {@hide} */
+ public Signature[] signatures;
+ /** {@hide} */
+ public long deltaSize = -1;
+ /** {@hide} */
+ public Bitmap icon;
+ /** {@hide} */
+ public String title;
+ /** {@hide} */
+ public Uri originatingUri;
+ /** {@hide} */
+ public Uri referrerUri;
+
+ public PackageInstallerParams() {
+ }
+
+ /** {@hide} */
+ public PackageInstallerParams(Parcel source) {
+ this.fullInstall = source.readInt() != 0;
+ this.installFlags = source.readInt();
+ this.installLocation = source.readInt();
+ this.signatures = (Signature[]) source.readParcelableArray(null);
+ deltaSize = source.readLong();
+ if (source.readInt() != 0) {
+ icon = Bitmap.CREATOR.createFromParcel(source);
+ }
+ title = source.readString();
+ originatingUri = Uri.CREATOR.createFromParcel(source);
+ referrerUri = Uri.CREATOR.createFromParcel(source);
+ }
+
+ public void setFullInstall(boolean fullInstall) {
+ this.fullInstall = fullInstall;
+ }
+
+ public void setInstallFlags(int installFlags) {
+ this.installFlags = installFlags;
+ }
+
+ public void setInstallLocation(int installLocation) {
+ this.installLocation = installLocation;
+ }
+
+ public void setSignatures(Signature[] signatures) {
+ this.signatures = signatures;
+ }
+
+ public void setDeltaSize(long deltaSize) {
+ this.deltaSize = deltaSize;
+ }
+
+ public void setIcon(Bitmap icon) {
+ this.icon = icon;
+ }
+
+ public void setTitle(CharSequence title) {
+ this.title = (title != null) ? title.toString() : null;
+ }
+
+ public void setOriginatingUri(Uri originatingUri) {
+ this.originatingUri = originatingUri;
+ }
+
+ public void setReferrerUri(Uri referrerUri) {
+ this.referrerUri = referrerUri;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(fullInstall ? 1 : 0);
+ dest.writeInt(installFlags);
+ dest.writeInt(installLocation);
+ dest.writeParcelableArray(signatures, flags);
+ dest.writeLong(deltaSize);
+ if (icon != null) {
+ dest.writeInt(1);
+ icon.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeString(title);
+ dest.writeParcelable(originatingUri, flags);
+ dest.writeParcelable(referrerUri, flags);
+ }
+
+ public static final Parcelable.Creator<PackageInstallerParams>
+ CREATOR = new Parcelable.Creator<PackageInstallerParams>() {
+ @Override
+ public PackageInstallerParams createFromParcel(Parcel p) {
+ return new PackageInstallerParams(p);
+ }
+
+ @Override
+ public PackageInstallerParams[] newArray(int size) {
+ return new PackageInstallerParams[size];
+ }
+ };
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index eb2c11f..c5cd5c9 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -17,6 +17,7 @@
package android.content.pm;
import android.annotation.IntDef;
+import android.annotation.PrivateApi;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.PackageInstallObserver;
@@ -369,6 +370,7 @@ public abstract class PackageManager {
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} on success.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_SUCCEEDED = 1;
/**
@@ -377,6 +379,7 @@ public abstract class PackageManager {
* already installed.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_ALREADY_EXISTS = -1;
/**
@@ -385,6 +388,7 @@ public abstract class PackageManager {
* file is invalid.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_INVALID_APK = -2;
/**
@@ -393,6 +397,7 @@ public abstract class PackageManager {
* is invalid.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_INVALID_URI = -3;
/**
@@ -401,6 +406,7 @@ public abstract class PackageManager {
* service found that the device didn't have enough storage space to install the app.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4;
/**
@@ -409,6 +415,7 @@ public abstract class PackageManager {
* package is already installed with the same name.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5;
/**
@@ -417,6 +424,7 @@ public abstract class PackageManager {
* the requested shared user does not exist.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_NO_SHARED_USER = -6;
/**
@@ -426,6 +434,7 @@ public abstract class PackageManager {
* than the new package (and the old package's data was not removed).
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7;
/**
@@ -435,6 +444,7 @@ public abstract class PackageManager {
* device and does not have matching signature.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8;
/**
@@ -443,6 +453,7 @@ public abstract class PackageManager {
* the new package uses a shared library that is not available.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9;
/**
@@ -451,6 +462,7 @@ public abstract class PackageManager {
* the new package uses a shared library that is not available.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10;
/**
@@ -460,6 +472,7 @@ public abstract class PackageManager {
* either because there was not enough storage or the validation failed.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_DEXOPT = -11;
/**
@@ -469,6 +482,7 @@ public abstract class PackageManager {
* that required by the package.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_OLDER_SDK = -12;
/**
@@ -478,6 +492,7 @@ public abstract class PackageManager {
* same authority as a provider already installed in the system.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13;
/**
@@ -487,6 +502,7 @@ public abstract class PackageManager {
* that required by the package.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_NEWER_SDK = -14;
/**
@@ -497,6 +513,7 @@ public abstract class PackageManager {
* flag.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_TEST_ONLY = -15;
/**
@@ -506,6 +523,7 @@ public abstract class PackageManager {
* compatible with the the device's CPU_ABI.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_CPU_ABI_INCOMPATIBLE = -16;
/**
@@ -514,6 +532,7 @@ public abstract class PackageManager {
* the new package uses a feature that is not available.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_MISSING_FEATURE = -17;
// ------ Errors related to sdcard
@@ -523,6 +542,7 @@ public abstract class PackageManager {
* a secure container mount point couldn't be accessed on external media.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_CONTAINER_ERROR = -18;
/**
@@ -532,6 +552,7 @@ public abstract class PackageManager {
* location.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19;
/**
@@ -541,6 +562,7 @@ public abstract class PackageManager {
* location because the media is not available.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20;
/**
@@ -549,6 +571,7 @@ public abstract class PackageManager {
* the new package couldn't be installed because the verification timed out.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_VERIFICATION_TIMEOUT = -21;
/**
@@ -557,6 +580,7 @@ public abstract class PackageManager {
* the new package couldn't be installed because the verification did not succeed.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_VERIFICATION_FAILURE = -22;
/**
@@ -565,6 +589,7 @@ public abstract class PackageManager {
* the package changed from what the calling program expected.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_PACKAGE_CHANGED = -23;
/**
@@ -590,6 +615,7 @@ public abstract class PackageManager {
* '.apk' extension.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_PARSE_FAILED_NOT_APK = -100;
/**
@@ -598,6 +624,7 @@ public abstract class PackageManager {
* if the parser was unable to retrieve the AndroidManifest.xml file.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101;
/**
@@ -606,6 +633,7 @@ public abstract class PackageManager {
* if the parser encountered an unexpected exception.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102;
/**
@@ -614,6 +642,7 @@ public abstract class PackageManager {
* if the parser did not find any certificates in the .apk.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103;
/**
@@ -622,6 +651,7 @@ public abstract class PackageManager {
* if the parser found inconsistent certificates on the files in the .apk.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104;
/**
@@ -631,6 +661,7 @@ public abstract class PackageManager {
* files in the .apk.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105;
/**
@@ -639,6 +670,7 @@ public abstract class PackageManager {
* if the parser encountered a bad or missing package name in the manifest.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106;
/**
@@ -647,6 +679,7 @@ public abstract class PackageManager {
* if the parser encountered a bad shared user id name in the manifest.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107;
/**
@@ -655,6 +688,7 @@ public abstract class PackageManager {
* if the parser encountered some structural problem in the manifest.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108;
/**
@@ -664,6 +698,7 @@ public abstract class PackageManager {
* in the manifest.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109;
/**
@@ -672,6 +707,7 @@ public abstract class PackageManager {
* if the system failed to install the package because of system issues.
* @hide
*/
+ @PrivateApi
public static final int INSTALL_FAILED_INTERNAL_ERROR = -110;
/**
@@ -1369,6 +1405,14 @@ public abstract class PackageManager {
public static final String FEATURE_MANAGEDPROFILES = "android.software.managedprofiles";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device has a full implementation of the android.webkit.* APIs. Devices
+ * lacking this feature will not have a functioning WebView implementation.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_WEBVIEW = "android.software.webview";
+
+ /**
* Action to external storage service to clean out removed apps.
* @hide
*/
@@ -2863,6 +2907,7 @@ public abstract class PackageManager {
* instead. This method will continue to be supported but the older observer interface
* will not get additional failure details.
*/
+ // @PrivateApi
public abstract void installPackage(
Uri packageURI, IPackageInstallObserver observer, int flags,
String installerPackageName);
@@ -2897,6 +2942,7 @@ public abstract class PackageManager {
* continue to be supported but the older observer interface will not get additional failure
* details.
*/
+ // @PrivateApi
public abstract void installPackageWithVerification(Uri packageURI,
IPackageInstallObserver observer, int flags, String installerPackageName,
Uri verificationURI, ManifestDigest manifestDigest,
@@ -3025,6 +3071,7 @@ public abstract class PackageManager {
* on the system for other users, also install it for the calling user.
* @hide
*/
+ // @PrivateApi
public abstract int installExistingPackage(String packageName)
throws NameNotFoundException;
@@ -3114,6 +3161,7 @@ public abstract class PackageManager {
*
* @hide
*/
+ // @PrivateApi
public abstract void deletePackage(
String packageName, IPackageDeleteObserver observer, int flags);
@@ -3182,6 +3230,7 @@ public abstract class PackageManager {
*
* @hide
*/
+ // @PrivateApi
public abstract void freeStorageAndNotify(long freeStorageSize, IPackageDataObserver observer);
/**
@@ -3510,6 +3559,9 @@ public abstract class PackageManager {
*/
public abstract VerifierDeviceIdentity getVerifierDeviceIdentity();
+ /** {@hide} */
+ public abstract PackageInstaller getPackageInstaller();
+
/**
* Returns the data directory for a particular user and package, given the uid of the package.
* @param uid uid of the package, including the userId and appId
@@ -3524,24 +3576,38 @@ public abstract class PackageManager {
}
/**
- * Adds a forwarding intent filter. After calling this method all intents sent from the user
- * with id userIdOrig can also be be resolved by activities in the user with id userIdDest if
- * they match the specified intent filter.
- * @param filter the {@link IntentFilter} the intent has to match to be forwarded
- * @param removable if set to false, {@link clearForwardingIntents} will not remove this intent
- * filter
- * @param userIdOrig user from which the intent can be forwarded
- * @param userIdDest user to which the intent can be forwarded
+ * Adds a {@link CrossProfileIntentFilter}. After calling this method all intents sent from the
+ * user with id sourceUserId can also be be resolved by activities in the user with id
+ * targetUserId if they match the specified intent filter.
+ * @param filter the {@link IntentFilter} the intent has to match
+ * @param removable if set to false, {@link clearCrossProfileIntentFilters} will not remove this
+ * {@link CrossProfileIntentFilter}
* @hide
*/
+ public abstract void addCrossProfileIntentFilter(IntentFilter filter, boolean removable,
+ int sourceUserId, int targetUserId);
+
+ /**
+ * @hide
+ * @deprecated
+ * TODO: remove it as soon as the code of ManagedProvisionning is updated
+ */
public abstract void addForwardingIntentFilter(IntentFilter filter, boolean removable,
- int userIdOrig, int userIdDest);
+ int sourceUserId, int targetUserId);
/**
- * Clearing all removable {@link ForwardingIntentFilter}s that are set with the given user as
- * the origin.
- * @param userIdOrig user from which the intent can be forwarded
+ * Clearing removable {@link CrossProfileIntentFilter}s which have the specified user as their
+ * source
+ * @param sourceUserId
+ * be cleared.
* @hide
*/
- public abstract void clearForwardingIntentFilters(int userIdOrig);
+ public abstract void clearCrossProfileIntentFilters(int sourceUserId);
+
+ /**
+ * @hide
+ * @deprecated
+ * TODO: remove it as soon as the code of ManagedProvisionning is updated
+ */
+ public abstract void clearForwardingIntentFilters(int sourceUserId);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index ff96c51..ab8bf61 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -16,6 +16,9 @@
package android.content.pm;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -32,6 +35,7 @@ import android.util.AttributeSet;
import android.util.Base64;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.TypedValue;
@@ -41,6 +45,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
@@ -51,7 +56,6 @@ 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;
import java.util.Iterator;
@@ -61,6 +65,7 @@ import java.util.Set;
import java.util.jar.StrictJarFile;
import java.util.zip.ZipEntry;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -207,16 +212,20 @@ public class PackageParser {
*/
public static class PackageLite {
public final String packageName;
+ public final String splitName;
public final int versionCode;
public final int installLocation;
public final VerifierInfo[] verifiers;
+ public final Signature[] signatures;
- public PackageLite(String packageName, int versionCode,
- int installLocation, List<VerifierInfo> verifiers) {
+ public PackageLite(String packageName, String splitName, int versionCode,
+ int installLocation, List<VerifierInfo> verifiers, Signature[] signatures) {
this.packageName = packageName;
+ this.splitName = splitName;
this.versionCode = versionCode;
this.installLocation = installLocation;
this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]);
+ this.signatures = signatures;
}
}
@@ -459,7 +468,7 @@ public class PackageParser {
return pi;
}
- private Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry je,
+ private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry je,
byte[] readBuffer) {
try {
// We must read the stream for the JarEntry to retrieve
@@ -486,6 +495,7 @@ public class PackageParser {
public final static int PARSE_ON_SDCARD = 1<<5;
public final static int PARSE_IS_SYSTEM_DIR = 1<<6;
public final static int PARSE_IS_PRIVILEGED = 1<<7;
+ public final static int PARSE_GET_SIGNATURES = 1<<8;
public int getParseError() {
return mParseError;
@@ -722,12 +732,8 @@ public class PackageParser {
mReadBuffer = readBufferRef;
}
- if (certs != null && certs.length > 0) {
- 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]);
- }
+ if (!ArrayUtils.isEmpty(certs)) {
+ pkg.mSignatures = convertToSignatures(certs);
} else {
Slog.e(TAG, "Package " + pkg.packageName
+ " has no certificates; ignoring!");
@@ -762,6 +768,39 @@ public class PackageParser {
return true;
}
+ /**
+ * Only collect certificates on the manifest; does not validate signatures
+ * across remainder of package.
+ */
+ private static Signature[] collectCertificates(String packageFilePath) {
+ try {
+ final StrictJarFile jarFile = new StrictJarFile(packageFilePath);
+ try {
+ final ZipEntry jarEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
+ if (jarEntry != null) {
+ final Certificate[][] certs = loadCertificates(jarFile, jarEntry, null);
+ return convertToSignatures(certs);
+ }
+ } finally {
+ jarFile.close();
+ }
+ } catch (GeneralSecurityException e) {
+ Slog.w(TAG, "Failed to collect certs from " + packageFilePath + ": " + e);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to collect certs from " + packageFilePath + ": " + e);
+ }
+ return null;
+ }
+
+ private static Signature[] convertToSignatures(Certificate[][] certs)
+ throws CertificateEncodingException {
+ final Signature[] res = new Signature[certs.length];
+ for (int i = 0; i < certs.length; i++) {
+ res[i] = new Signature(certs[i]);
+ }
+ return res;
+ }
+
/*
* Utility method that retrieves just the package name and install
* location from the apk location at the given file path.
@@ -794,11 +833,22 @@ public class PackageParser {
return null;
}
+ // Only collect certificates on the manifest; does not validate
+ // signatures across remainder of package.
+ final Signature[] signatures;
+ if ((flags & PARSE_GET_SIGNATURES) != 0) {
+ signatures = collectCertificates(packageFilePath);
+ } else {
+ signatures = null;
+ }
+
final AttributeSet attrs = parser;
final String errors[] = new String[1];
PackageLite packageLite = null;
try {
- packageLite = parsePackageLite(res, parser, attrs, flags, errors);
+ packageLite = parsePackageLite(res, parser, attrs, flags, signatures, errors);
+ } catch (PackageParserException e) {
+ Slog.w(TAG, packageFilePath, e);
} catch (IOException e) {
Slog.w(TAG, packageFilePath, e);
} catch (XmlPullParserException e) {
@@ -840,72 +890,51 @@ public class PackageParser {
? null : "must have at least one '.' separator";
}
- private static String parsePackageName(XmlPullParser parser,
- AttributeSet attrs, int flags, String[] outError)
- throws IOException, XmlPullParserException {
+ private static Pair<String, String> parsePackageSplitNames(XmlPullParser parser,
+ AttributeSet attrs, int flags) throws IOException, XmlPullParserException,
+ PackageParserException {
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
- ;
}
if (type != XmlPullParser.START_TAG) {
- outError[0] = "No start tag found";
- return null;
+ throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "No start tag found");
}
- if (DEBUG_PARSER)
- Slog.v(TAG, "Root element name: '" + parser.getName() + "'");
if (!parser.getName().equals("manifest")) {
- outError[0] = "No <manifest> tag";
- return null;
+ throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "No <manifest> tag");
}
- String pkgName = attrs.getAttributeValue(null, "package");
- if (pkgName == null || pkgName.length() == 0) {
- outError[0] = "<manifest> does not specify package";
- return null;
+
+ final String packageName = attrs.getAttributeValue(null, "package");
+ if (!"android".equals(packageName)) {
+ final String error = validateName(packageName, true);
+ if (error != null) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
+ "Invalid manifest package: " + error);
+ }
}
- String nameError = validateName(pkgName, true);
- if (nameError != null && !"android".equals(pkgName)) {
- outError[0] = "<manifest> specifies bad package name \""
- + pkgName + "\": " + nameError;
- return null;
+
+ final String splitName = attrs.getAttributeValue(null, "split");
+ if (splitName != null) {
+ final String error = validateName(splitName, true);
+ if (error != null) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
+ "Invalid manifest split: " + error);
+ }
}
- return pkgName.intern();
+ return Pair.create(packageName.intern(),
+ (splitName != null) ? splitName.intern() : splitName);
}
private static PackageLite parsePackageLite(Resources res, XmlPullParser parser,
- AttributeSet attrs, int flags, String[] outError) throws IOException,
- XmlPullParserException {
+ AttributeSet attrs, int flags, Signature[] signatures, String[] outError)
+ throws IOException, XmlPullParserException, PackageParserException {
+ final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs, flags);
- int type;
- while ((type = parser.next()) != XmlPullParser.START_TAG
- && type != XmlPullParser.END_DOCUMENT) {
- ;
- }
-
- if (type != XmlPullParser.START_TAG) {
- outError[0] = "No start tag found";
- return null;
- }
- if (DEBUG_PARSER)
- Slog.v(TAG, "Root element name: '" + parser.getName() + "'");
- if (!parser.getName().equals("manifest")) {
- outError[0] = "No <manifest> tag";
- return null;
- }
- String pkgName = attrs.getAttributeValue(null, "package");
- if (pkgName == null || pkgName.length() == 0) {
- outError[0] = "<manifest> does not specify package";
- return null;
- }
- String nameError = validateName(pkgName, true);
- if (nameError != null && !"android".equals(pkgName)) {
- outError[0] = "<manifest> specifies bad package name \""
- + pkgName + "\": " + nameError;
- return null;
- }
int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
int versionCode = 0;
int numFound = 0;
@@ -925,6 +954,7 @@ public class PackageParser {
}
// Only search the tree when the tag is directly below <manifest>
+ int type;
final int searchDepth = parser.getDepth() + 1;
final List<VerifierInfo> verifiers = new ArrayList<VerifierInfo>();
@@ -942,7 +972,8 @@ public class PackageParser {
}
}
- return new PackageLite(pkgName.intern(), versionCode, installLocation, verifiers);
+ return new PackageLite(packageSplit.first, packageSplit.second, versionCode,
+ installLocation, verifiers, signatures);
}
/**
@@ -966,12 +997,18 @@ public class PackageParser {
mParseActivityArgs = null;
mParseServiceArgs = null;
mParseProviderArgs = null;
-
- String pkgName = parsePackageName(parser, attrs, flags, outError);
- if (pkgName == null) {
+
+ final String pkgName;
+ final String splitName;
+ try {
+ Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs, flags);
+ pkgName = packageSplit.first;
+ splitName = packageSplit.second;
+ } catch (PackageParserException e) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
return null;
}
+
int type;
if (mOnlyCoreApps) {
@@ -982,9 +1019,9 @@ public class PackageParser {
}
}
- final Package pkg = new Package(pkgName);
+ final Package pkg = new Package(pkgName, splitName);
boolean foundApp = false;
-
+
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifest);
pkg.mVersionCode = pkg.applicationInfo.versionCode = sa.getInteger(
@@ -2137,7 +2174,6 @@ public class PackageParser {
}
final int innerDepth = parser.getDepth();
-
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
@@ -2511,13 +2547,13 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestActivity_singleUser,
false)) {
a.info.flags |= ActivityInfo.FLAG_SINGLE_USER;
- if (a.info.exported) {
+ if (a.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
Slog.w(TAG, "Activity exported request ignored due to singleUser: "
+ a.className + " at " + mArchiveSourcePath + " "
+ parser.getPositionDescription());
a.info.exported = false;
+ setExported = true;
}
- setExported = true;
}
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestActivity_primaryUserOnly,
@@ -2870,7 +2906,7 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestProvider_singleUser,
false)) {
p.info.flags |= ProviderInfo.FLAG_SINGLE_USER;
- if (p.info.exported) {
+ if (p.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
Slog.w(TAG, "Provider exported request ignored due to singleUser: "
+ p.className + " at " + mArchiveSourcePath + " "
+ parser.getPositionDescription());
@@ -3144,13 +3180,13 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestService_singleUser,
false)) {
s.info.flags |= ServiceInfo.FLAG_SINGLE_USER;
- if (s.info.exported) {
+ if (s.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
Slog.w(TAG, "Service exported request ignored due to singleUser: "
+ s.className + " at " + mArchiveSourcePath + " "
+ parser.getPositionDescription());
s.info.exported = false;
+ setExported = true;
}
- setExported = true;
}
sa.recycle();
@@ -3544,6 +3580,7 @@ public class PackageParser {
public final static class Package {
public String packageName;
+ public String splitName;
// For now we only support one application per package.
public final ApplicationInfo applicationInfo = new ApplicationInfo();
@@ -3660,9 +3697,10 @@ public class PackageParser {
public Set<PublicKey> mSigningKeys;
public Map<String, Set<PublicKey>> mKeySetMapping;
- public Package(String _name) {
- packageName = _name;
- applicationInfo.packageName = _name;
+ public Package(String packageName, String splitName) {
+ this.packageName = packageName;
+ this.splitName = splitName;
+ applicationInfo.packageName = packageName;
applicationInfo.uid = -1;
}
@@ -4267,4 +4305,13 @@ public class PackageParser {
public static void setCompatibilityModeEnabled(boolean compatibilityModeEnabled) {
sCompatibilityModeEnabled = compatibilityModeEnabled;
}
+
+ public static class PackageParserException extends Exception {
+ public final int error;
+
+ public PackageParserException(int error, String detailMessage) {
+ super(detailMessage);
+ this.error = error;
+ }
+ }
}
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index f4e7dc3..96aa083 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -31,8 +31,10 @@ import java.security.cert.CertificateFactory;
import java.util.Arrays;
/**
- * Opaque, immutable representation of a signature associated with an
+ * Opaque, immutable representation of a signing certificate associated with an
* application package.
+ * <p>
+ * This class name is slightly misleading, since it's not actually a signature.
*/
public class Signature implements Parcelable {
private final byte[] mSignature;
diff --git a/core/java/android/content/pm/VerificationParams.java b/core/java/android/content/pm/VerificationParams.java
index 22e1a85..bf1f77f 100644
--- a/core/java/android/content/pm/VerificationParams.java
+++ b/core/java/android/content/pm/VerificationParams.java
@@ -24,8 +24,10 @@ import android.os.Parcelable;
/**
* Represents verification parameters used to verify packages to be installed.
*
+ * @deprecated callers should migrate to {@link PackageInstallerParams}.
* @hide
*/
+@Deprecated
public class VerificationParams implements Parcelable {
/** A constant used to indicate that a uid value is not present. */
public static final int NO_UID = -1;
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 5674154..3f01dd2 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -64,7 +64,6 @@ import java.util.Arrays;
* List Resource</a>.</p>
*/
public class ColorStateList implements Parcelable {
-
private int[][] mStateSpecs; // must be parallel to mColors
private int[] mColors; // must be parallel to mStateSpecs
private int mDefaultColor = 0xffff0000;
@@ -100,9 +99,9 @@ public class ColorStateList implements Parcelable {
public static ColorStateList valueOf(int color) {
// TODO: should we collect these eventually?
synchronized (sCache) {
- WeakReference<ColorStateList> ref = sCache.get(color);
- ColorStateList csl = ref != null ? ref.get() : null;
+ final WeakReference<ColorStateList> ref = sCache.get(color);
+ ColorStateList csl = ref != null ? ref.get() : null;
if (csl != null) {
return csl;
}
@@ -118,8 +117,7 @@ public class ColorStateList implements Parcelable {
*/
public static ColorStateList createFromXml(Resources r, XmlPullParser parser)
throws XmlPullParserException, IOException {
-
- AttributeSet attrs = Xml.asAttributeSet(parser);
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
while ((type=parser.next()) != XmlPullParser.START_TAG
@@ -133,22 +131,22 @@ public class ColorStateList implements Parcelable {
return createFromXmlInner(r, parser, attrs);
}
- /* Create from inside an XML document. Called on a parser positioned at
- * a tag in an XML document, tries to create a ColorStateList from that tag.
- * Returns null if the tag is not a valid ColorStateList.
+ /**
+ * Create from inside an XML document. Called on a parser positioned at a
+ * tag in an XML document, tries to create a ColorStateList from that tag.
+ *
+ * @throws XmlPullParserException if the current tag is not &lt;selector>
+ * @return A color state list for the current tag.
*/
private static ColorStateList createFromXmlInner(Resources r, XmlPullParser parser,
AttributeSet attrs) throws XmlPullParserException, IOException {
-
- ColorStateList colorStateList;
-
+ final ColorStateList colorStateList;
final String name = parser.getName();
-
if (name.equals("selector")) {
colorStateList = new ColorStateList();
} else {
throw new XmlPullParserException(
- parser.getPositionDescription() + ": invalid drawable tag " + name);
+ parser.getPositionDescription() + ": invalid drawable tag " + name);
}
colorStateList.inflate(r, parser, attrs);
@@ -161,9 +159,8 @@ public class ColorStateList implements Parcelable {
* (0-255).
*/
public ColorStateList withAlpha(int alpha) {
- int[] colors = new int[mColors.length];
-
- int len = colors.length;
+ final int[] colors = new int[mColors.length];
+ final int len = colors.length;
for (int i = 0; i < len; i++) {
colors[i] = (mColors[i] & 0xFFFFFF) | (alpha << 24);
}
@@ -176,7 +173,6 @@ public class ColorStateList implements Parcelable {
*/
private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
throws XmlPullParserException, IOException {
-
int type;
final int innerDepth = parser.getDepth()+1;
@@ -259,10 +255,25 @@ public class ColorStateList implements Parcelable {
System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize);
}
+ /**
+ * Indicates whether this color state list contains more than one state spec
+ * and will change color based on state.
+ *
+ * @return True if this color state list changes color based on state, false
+ * otherwise.
+ * @see #getColorForState(int[], int)
+ */
public boolean isStateful() {
return mStateSpecs.length > 1;
}
+ /**
+ * Indicates whether this color state list is opaque, which means that every
+ * color returned from {@link #getColorForState(int[], int)} has an alpha
+ * value of 255.
+ *
+ * @return True if this color state list is opaque.
+ */
public boolean isOpaque() {
final int n = mColors.length;
for (int i = 0; i < n; i++) {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 499de17..ed3f9aa 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -21,6 +21,7 @@ import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.Nullable;
import android.content.pm.ActivityInfo;
import android.graphics.Movie;
import android.graphics.drawable.Drawable;
@@ -30,11 +31,11 @@ import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Trace;
+import android.util.ArrayMap;
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;
@@ -103,10 +104,10 @@ public class Resources {
// 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 ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mDrawableCache =
+ new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>();
+ private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mColorDrawableCache =
+ new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>();
private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache =
new LongSparseArray<WeakReference<ColorStateList>>();
@@ -701,12 +702,17 @@ public class Resources {
* Context.obtainStyledAttributes} with
* an array containing the resource ID of interest to create the TypedArray.</p>
*
+ * <p class="note"><strong>Note:</strong> To obtain a themed drawable, use
+ * {@link android.content.Context#getDrawable(int) Context.getDrawable(int)}
+ * or {@link #getDrawable(int, Theme)} passing the desired theme.</p>
+ *
* @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.
+ * @see #getDrawable(int, Theme)
*/
public Drawable getDrawable(int id) throws NotFoundException {
return getDrawable(id, null);
@@ -714,17 +720,19 @@ public class Resources {
/**
* Return a drawable object associated with a particular resource ID and
- * styled for the specified theme.
+ * styled for the specified theme. Various types of objects will be
+ * returned depending on the underlying resource -- for example, a solid
+ * color, PNG image, scalable image, etc.
*
* @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.
+ * @param theme The theme used to style the drawable attributes, may be {@code null}.
* @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 {
+ public Drawable getDrawable(int id, @Nullable Theme theme) throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -754,6 +762,11 @@ public class Resources {
* image, scalable image, etc. The Drawable API hides these implementation
* details.
*
+ * <p class="note"><strong>Note:</strong> To obtain a themed drawable, use
+ * {@link android.content.Context#getDrawable(int) Context.getDrawable(int)}
+ * or {@link #getDrawableForDensity(int, int, Theme)} passing the desired
+ * theme.</p>
+ *
* @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.
@@ -777,12 +790,12 @@ public class Resources {
* 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.
+ * @param theme The theme used to style the drawable attributes, may be {@code null}.
* @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) {
+ public Drawable getDrawableForDensity(int id, int density, @Nullable Theme theme) {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -1260,18 +1273,17 @@ public class Resources {
* any of the style's attributes are already defined in the theme, the
* current values in the theme will be overwritten.
*
- * @param resid The resource ID of a style resource from which to
+ * @param resId The resource ID of a style resource from which to
* obtain attribute values.
* @param force If true, values in the style resource will always be
* used in the theme; otherwise, they will only be used
* if not already defined in the theme.
*/
- public void applyStyle(int resid, boolean force) {
- AssetManager.applyThemeStyle(mTheme, resid, force);
+ 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;
+ mThemeResId = resId;
+ mKey += Integer.toHexString(resId) + (force ? "! " : " ");
}
/**
@@ -1287,6 +1299,7 @@ public class Resources {
AssetManager.copyTheme(mTheme, other.mTheme);
mThemeResId = other.mThemeResId;
+ mKey = other.mKey;
}
/**
@@ -1475,21 +1488,12 @@ public class Resources {
* 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) {
+ public TypedArray resolveAttributes(int[] values, int[] attrs) {
final int len = attrs.length;
if (values != null && len != values.length) {
throw new IllegalArgumentException(
@@ -1497,8 +1501,7 @@ public class Resources {
}
final TypedArray array = TypedArray.obtain(Resources.this, len);
- AssetManager.resolveAttrs(mTheme, defStyleAttr, defStyleRes,
- values, attrs, array.mData, array.mIndices);
+ AssetManager.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
array.mTheme = this;
array.mXml = null;
@@ -1586,6 +1589,9 @@ public class Resources {
/** Resource identifier for the theme. */
private int mThemeResId = 0;
+ /** Unique key for the series of styles applied to this theme. */
+ private String mKey = "";
+
// Needed by layoutlib.
/*package*/ long getNativeTheme() {
return mTheme;
@@ -1594,6 +1600,10 @@ public class Resources {
/*package*/ int getAppliedStyleResId() {
return mThemeResId;
}
+
+ /*package*/ String getKey() {
+ return mKey;
+ }
}
/**
@@ -1749,7 +1759,8 @@ public class Resources {
}
private void clearDrawableCachesLocked(
- ThemedCaches<ConstantState> caches, int configChanges) {
+ ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches,
+ int configChanges) {
final int N = caches.size();
for (int i = 0; i < N; i++) {
clearDrawableCacheLocked(caches.valueAt(i), configChanges);
@@ -1772,7 +1783,7 @@ public class Resources {
configChanges, cs.getChangingConfigurations())) {
if (DEBUG_CONFIG) {
Log.d(TAG, "FLUSHING #0x"
- + Long.toHexString(mDrawableCache.keyAt(i))
+ + Long.toHexString(cache.keyAt(i))
+ " / " + cs + " with changes: 0x"
+ Integer.toHexString(cs.getChangingConfigurations()));
}
@@ -2214,7 +2225,7 @@ public class Resources {
}
final boolean isColorDrawable;
- final ThemedCaches<ConstantState> caches;
+ final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches;
final long key;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
&& value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
@@ -2267,7 +2278,8 @@ public class Resources {
}
private void cacheDrawable(TypedValue value, Theme theme, boolean isColorDrawable,
- ThemedCaches<ConstantState> caches, long key, Drawable dr) {
+ ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches,
+ long key, Drawable dr) {
final ConstantState cs = dr.getConstantState();
if (cs == null) {
return;
@@ -2296,8 +2308,12 @@ public class Resources {
}
} else {
synchronized (mAccessLock) {
- final LongSparseArray<WeakReference<ConstantState>> themedCache;
- themedCache = caches.getOrCreate(theme == null ? 0 : theme.mThemeResId);
+ final String themeKey = theme == null ? "" : theme.mKey;
+ LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(themeKey);
+ if (themedCache == null) {
+ themedCache = new LongSparseArray<WeakReference<ConstantState>>(1);
+ caches.put(themeKey, themedCache);
+ }
themedCache.put(key, new WeakReference<ConstantState>(cs));
}
}
@@ -2336,12 +2352,12 @@ public class Resources {
if (file.endsWith(".xml")) {
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
- dr = Drawable.createFromXmlThemed(this, rp, theme);
+ dr = Drawable.createFromXml(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);
+ dr = Drawable.createFromResourceStream(this, value, is, file, null);
is.close();
}
} catch (Exception e) {
@@ -2356,7 +2372,9 @@ public class Resources {
return dr;
}
- private Drawable getCachedDrawable(ThemedCaches<ConstantState> caches, long key, Theme theme) {
+ private Drawable getCachedDrawable(
+ ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches,
+ long key, Theme theme) {
synchronized (mAccessLock) {
final int themeKey = theme != null ? theme.mThemeResId : 0;
final LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(themeKey);
@@ -2593,21 +2611,4 @@ 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 15337ce..20dcf83 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -885,13 +885,13 @@ public class TypedArray {
/**
* Extracts theme attributes from a typed array for later resolution using
- * {@link Theme#resolveAttributes(int[], int[], int, int)}.
+ * {@link Theme#resolveAttributes(int[], int[])}. Removes the entries from
+ * the typed array so that subsequent calls to typed getters will return the
+ * default value without crashing.
*
- * @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
+ * attributes, or null if there are no theme attributes in the typed
+ * array
* @hide
*/
public int[] extractThemeAttrs() {
@@ -901,15 +901,27 @@ public class TypedArray {
int[] attrs = null;
+ final int[] data = mData;
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;
+ final int index = i * AssetManager.STYLE_NUM_ENTRIES;
+ if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) {
+ continue;
+ }
+
+ // Null the entry so that we can safely call getZzz().
+ data[index + AssetManager.STYLE_TYPE] = TypedValue.TYPE_NULL;
+
+ final int attr = data[index + AssetManager.STYLE_DATA];
+ if (attr == 0) {
+ // This attribute is useless!
+ continue;
+ }
+
+ if (attrs == null) {
+ attrs = new int[N];
}
+ attrs[i] = attr;
}
return attrs;
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index 197e3ff..a75372f 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -42,12 +42,8 @@ import android.util.LongSparseArray;
public class CursorWindow extends SQLiteClosable implements Parcelable {
private static final String STATS_TAG = "CursorWindowStats";
- /** The cursor window size. resource xml file specifies the value in kB.
- * convert it to bytes here by multiplying with 1024.
- */
- private static final int sCursorWindowSize =
- Resources.getSystem().getInteger(
- com.android.internal.R.integer.config_cursorWindowSize) * 1024;
+ // This static member will be evaluated when first used.
+ private static int sCursorWindowSize = -1;
/**
* The native CursorWindow object pointer. (FOR INTERNAL USE ONLY)
@@ -100,6 +96,13 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
public CursorWindow(String name) {
mStartPos = 0;
mName = name != null && name.length() != 0 ? name : "<unnamed>";
+ if (sCursorWindowSize < 0) {
+ /** The cursor window size. resource xml file specifies the value in kB.
+ * convert it to bytes here by multiplying with 1024.
+ */
+ sCursorWindowSize = Resources.getSystem().getInteger(
+ com.android.internal.R.integer.config_cursorWindowSize) * 1024;
+ }
mWindowPtr = nativeCreate(mName, sCursorWindowSize);
if (mWindowPtr == 0) {
throw new CursorWindowAllocationException("Cursor window allocation of " +
diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
index 220b40d..2dce425 100644
--- a/core/java/android/ddm/DdmHandleHello.java
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -22,6 +22,7 @@ import org.apache.harmony.dalvik.ddmc.DdmServer;
import android.util.Log;
import android.os.Debug;
import android.os.UserHandle;
+import dalvik.system.VMRuntime;
import java.nio.ByteBuffer;
@@ -126,8 +127,21 @@ public class DdmHandleHello extends ChunkHandler {
// appName = "unknown";
String appName = DdmHandleAppName.getAppName();
- ByteBuffer out = ByteBuffer.allocate(20
- + vmIdent.length()*2 + appName.length()*2);
+ VMRuntime vmRuntime = VMRuntime.getRuntime();
+ String instructionSetDescription =
+ vmRuntime.is64Bit() ? "64-bit" : "32-bit";
+ String vmInstructionSet = vmRuntime.vmInstructionSet();
+ if (vmInstructionSet != null && vmInstructionSet.length() > 0) {
+ instructionSetDescription += " (" + vmInstructionSet + ")";
+ }
+ String vmFlags = "CheckJNI="
+ + (vmRuntime.isCheckJniEnabled() ? "true" : "false");
+
+ ByteBuffer out = ByteBuffer.allocate(28
+ + vmIdent.length() * 2
+ + appName.length() * 2
+ + instructionSetDescription.length() * 2
+ + vmFlags.length() * 2);
out.order(ChunkHandler.CHUNK_ORDER);
out.putInt(DdmServer.CLIENT_PROTOCOL_VERSION);
out.putInt(android.os.Process.myPid());
@@ -136,6 +150,10 @@ public class DdmHandleHello extends ChunkHandler {
putString(out, vmIdent);
putString(out, appName);
out.putInt(UserHandle.myUserId());
+ out.putInt(instructionSetDescription.length());
+ putString(out, instructionSetDescription);
+ out.putInt(vmFlags.length());
+ putString(out, vmFlags);
Chunk reply = new Chunk(CHUNK_HELO, out);
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 35c86e7..0705e0c 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -169,6 +169,10 @@ public class Camera {
private boolean mFaceDetectionRunning = false;
private Object mAutoFocusCallbackLock = new Object();
+ private static final int NO_ERROR = 0;
+ private static final int EACCESS = -13;
+ private static final int ENODEV = -19;
+
/**
* Broadcast Action: A new picture is taken by the camera, and the entry of
* the picture has been added to the media store.
@@ -328,6 +332,24 @@ public class Camera {
}
Camera(int cameraId) {
+ int err = cameraInit(cameraId);
+ if (checkInitErrors(err)) {
+ switch(err) {
+ case EACCESS:
+ throw new RuntimeException("Fail to connect to camera service");
+ case ENODEV:
+ throw new RuntimeException("Camera initialization failed");
+ default:
+ // Should never hit this.
+ throw new RuntimeException("Unknown camera error");
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public int cameraInit(int cameraId) {
mShutterCallback = null;
mRawImageCallback = null;
mJpegCallback = null;
@@ -347,7 +369,21 @@ public class Camera {
String packageName = ActivityThread.currentPackageName();
- native_setup(new WeakReference<Camera>(this), cameraId, packageName);
+ return native_setup(new WeakReference<Camera>(this), cameraId, packageName);
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean checkInitErrors(int err) {
+ return err != NO_ERROR;
+ }
+
+ /**
+ * @hide
+ */
+ public static Camera openUninitialized() {
+ return new Camera();
}
/**
@@ -360,7 +396,7 @@ public class Camera {
release();
}
- private native final void native_setup(Object camera_this, int cameraId,
+ private native final int native_setup(Object camera_this, int cameraId,
String packageName);
private native final void native_release();
@@ -458,13 +494,16 @@ public class Camera {
*/
public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
if (holder != null) {
- setPreviewDisplay(holder.getSurface());
+ setPreviewSurface(holder.getSurface());
} else {
- setPreviewDisplay((Surface)null);
+ setPreviewSurface((Surface)null);
}
}
- private native final void setPreviewDisplay(Surface surface) throws IOException;
+ /**
+ * @hide
+ */
+ public native final void setPreviewSurface(Surface surface) throws IOException;
/**
* Sets the {@link SurfaceTexture} to be used for live preview.
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 4bea9ee..c8de2f1 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -142,9 +142,10 @@ public final class Sensor {
public static final String STRING_TYPE_TEMPERATURE = "android.sensor.temperature";
/**
- * A constant describing a proximity sensor type.
+ * A constant describing a proximity sensor type. This is a wake up sensor.
* <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
* for more details.
+ * @see #isWakeUpSensor()
*/
public static final int TYPE_PROXIMITY = 8;
@@ -307,8 +308,10 @@ public final class Sensor {
* itself. The sensor continues to operate while the device is asleep
* and will automatically wake the device to notify when significant
* motion is detected. The application does not need to hold any wake
- * locks for this sensor to trigger.
+ * locks for this sensor to trigger. This is a wake up sensor.
* <p>See {@link TriggerEvent} for more details.
+ *
+ * @see #isWakeUpSensor()
*/
public static final int TYPE_SIGNIFICANT_MOTION = 17;
@@ -381,11 +384,17 @@ public final class Sensor {
/**
* A constant describing a heart rate monitor.
* <p>
- * A sensor that measures the heart rate in beats per minute.
+ * The reported value is the heart rate in beats per minute.
+ * <p>
+ * The reported accuracy represents the status of the monitor during the reading. See the
+ * {@code SENSOR_STATUS_*} constants in {@link android.hardware.SensorManager SensorManager}
+ * for more details on accuracy/status values. In particular, when the accuracy is
+ * {@code SENSOR_STATUS_UNRELIABLE} or {@code SENSOR_STATUS_NO_CONTACT}, the heart rate
+ * value should be discarded.
* <p>
- * value[0] represents the beats per minute when the measurement was taken.
- * value[0] is 0 if the heart rate monitor could not measure the rate or the
- * rate is 0 beat per minute.
+ * This sensor requires permission {@code android.permission.BODY_SENSORS}.
+ * It will not be returned by {@code SensorManager.getSensorsList} nor
+ * {@code SensorManager.getDefaultSensor} if the application doesn't have this permission.
*/
public static final int TYPE_HEART_RATE = 21;
@@ -397,6 +406,348 @@ public final class Sensor {
public static final String STRING_TYPE_HEART_RATE = "android.sensor.heart_rate";
/**
+ * A non-wake up variant of proximity sensor.
+ *
+ * @see #TYPE_PROXIMITY
+ */
+ public static final int TYPE_NON_WAKE_UP_PROXIMITY_SENSOR = 22;
+
+ /**
+ * A constant string describing a non-wake up proximity sensor type.
+ *
+ * @see #TYPE_NON_WAKE_UP_PROXIMITY_SENSOR
+ */
+ public static final String SENSOR_STRING_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR =
+ "android.sensor.non_wake_up_proximity_sensor";
+
+ /**
+ * A constant describing a wake up variant of accelerometer sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_ACCELEROMETER
+ */
+ public static final int TYPE_WAKE_UP_ACCELEROMETER = 23;
+
+ /**
+ * A constant string describing a wake up accelerometer.
+ *
+ * @see #TYPE_WAKE_UP_ACCELEROMETER
+ */
+ public static final String STRING_TYPE_WAKE_UP_ACCELEROMETER =
+ "android.sensor.wake_up_accelerometer";
+
+ /**
+ * A constant describing a wake up variant of a magnetic field sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_MAGNETIC_FIELD
+ */
+ public static final int TYPE_WAKE_UP_MAGNETIC_FIELD = 24;
+
+ /**
+ * A constant string describing a wake up magnetic field sensor.
+ *
+ * @see #TYPE_WAKE_UP_MAGNETIC_FIELD
+ */
+ public static final String STRING_TYPE_WAKE_UP_MAGNETIC_FIELD =
+ "android.sensor.wake_up_magnetic_field";
+
+ /**
+ * A constant describing a wake up variant of an orientation sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_ORIENTATION
+ */
+ public static final int TYPE_WAKE_UP_ORIENTATION = 25;
+
+ /**
+ * A constant string describing a wake up orientation sensor.
+ *
+ * @see #TYPE_WAKE_UP_ORIENTATION
+ */
+ public static final String STRING_TYPE_WAKE_UP_ORIENTATION =
+ "android.sensor.wake_up_orientation";
+
+ /**
+ * A constant describing a wake up variant of a gyroscope sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_GYROSCOPE
+ */
+ public static final int TYPE_WAKE_UP_GYROSCOPE = 26;
+
+ /**
+ * A constant string describing a wake up gyroscope sensor type.
+ *
+ * @see #TYPE_WAKE_UP_GYROSCOPE
+ */
+ public static final String STRING_TYPE_WAKE_UP_GYROSCOPE = "android.sensor.wake_up_gyroscope";
+
+ /**
+ * A constant describing a wake up variant of a light sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_LIGHT
+ */
+ public static final int TYPE_WAKE_UP_LIGHT = 27;
+
+ /**
+ * A constant string describing a wake up light sensor type.
+ *
+ * @see #TYPE_WAKE_UP_LIGHT
+ */
+ public static final String STRING_TYPE_WAKE_UP_LIGHT = "android.sensor.wake_up_light";
+
+ /**
+ * A constant describing a wake up variant of a pressure sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_PRESSURE
+ */
+ public static final int TYPE_WAKE_UP_PRESSURE = 28;
+
+ /**
+ * A constant string describing a wake up pressure sensor type.
+ *
+ * @see #TYPE_WAKE_UP_PRESSURE
+ */
+ public static final String STRING_TYPE_WAKE_UP_PRESSURE = "android.sensor.wake_up_pressure";
+
+ /**
+ * A constant describing a wake up variant of a gravity sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_GRAVITY
+ */
+ public static final int TYPE_WAKE_UP_GRAVITY = 29;
+
+ /**
+ * A constant string describing a wake up gravity sensor type.
+ *
+ * @see #TYPE_WAKE_UP_GRAVITY
+ */
+ public static final String STRING_TYPE_WAKE_UP_GRAVITY = "android.sensor.wake_up_gravity";
+
+ /**
+ * A constant describing a wake up variant of a linear acceleration sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_LINEAR_ACCELERATION
+ */
+ public static final int TYPE_WAKE_UP_LINEAR_ACCELERATION = 30;
+
+ /**
+ * A constant string describing a wake up linear acceleration sensor type.
+ *
+ * @see #TYPE_WAKE_UP_LINEAR_ACCELERATION
+ */
+ public static final String STRING_TYPE_WAKE_UP_LINEAR_ACCELERATION =
+ "android.sensor.wake_up_linear_acceleration";
+
+ /**
+ * A constant describing a wake up variant of a rotation vector sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_ROTATION_VECTOR
+ */
+ public static final int TYPE_WAKE_UP_ROTATION_VECTOR = 31;
+
+ /**
+ * A constant string describing a wake up rotation vector sensor type.
+ *
+ * @see #TYPE_WAKE_UP_ROTATION_VECTOR
+ */
+ public static final String STRING_TYPE_WAKE_UP_ROTATION_VECTOR =
+ "android.sensor.wake_up_rotation_vector";
+
+ /**
+ * A constant describing a wake up variant of a relative humidity sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_RELATIVE_HUMIDITY
+ */
+ public static final int TYPE_WAKE_UP_RELATIVE_HUMIDITY = 32;
+
+ /**
+ * A constant string describing a wake up relative humidity sensor type.
+ *
+ * @see #TYPE_WAKE_UP_RELATIVE_HUMIDITY
+ */
+ public static final String STRING_TYPE_WAKE_UP_RELATIVE_HUMIDITY =
+ "android.sensor.wake_up_relative_humidity";
+
+ /**
+ * A constant describing a wake up variant of an ambient temperature sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_AMBIENT_TEMPERATURE
+ */
+ public static final int TYPE_WAKE_UP_AMBIENT_TEMPERATURE = 33;
+
+ /**
+ * A constant string describing a wake up ambient temperature sensor type.
+ *
+ * @see #TYPE_WAKE_UP_AMBIENT_TEMPERATURE
+ */
+ public static final String STRING_TYPE_WAKE_UP_AMBIENT_TEMPERATURE =
+ "android.sensor.wake_up_ambient_temperature";
+
+ /**
+ * A constant describing a wake up variant of an uncalibrated magnetic field sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_MAGNETIC_FIELD_UNCALIBRATED
+ */
+ public static final int TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED = 34;
+
+ /**
+ * A constant string describing a wake up uncalibrated magnetic field sensor type.
+ *
+ * @see #TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED
+ */
+ public static final String STRING_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED =
+ "android.sensor.wake_up_magnetic_field_uncalibrated";
+
+ /**
+ * A constant describing a wake up variant of a game rotation vector sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_GAME_ROTATION_VECTOR
+ */
+ public static final int TYPE_WAKE_UP_GAME_ROTATION_VECTOR = 35;
+
+ /**
+ * A constant string describing a wake up game rotation vector sensor type.
+ *
+ * @see #TYPE_WAKE_UP_GAME_ROTATION_VECTOR
+ */
+ public static final String STRING_TYPE_WAKE_UP_GAME_ROTATION_VECTOR =
+ "android.sensor.wake_up_game_rotation_vector";
+
+ /**
+ * A constant describing a wake up variant of an uncalibrated gyroscope sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_GYROSCOPE_UNCALIBRATED
+ */
+ public static final int TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED = 36;
+
+ /**
+ * A constant string describing a wake up uncalibrated gyroscope sensor type.
+ *
+ * @see #TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED
+ */
+ public static final String STRING_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED =
+ "android.sensor.wake_up_gyroscope_uncalibrated";
+
+ /**
+ * A constant describing a wake up variant of a step detector sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_STEP_DETECTOR
+ */
+ public static final int TYPE_WAKE_UP_STEP_DETECTOR = 37;
+
+ /**
+ * A constant string describing a wake up step detector sensor type.
+ *
+ * @see #TYPE_WAKE_UP_STEP_DETECTOR
+ */
+ public static final String STRING_TYPE_WAKE_UP_STEP_DETECTOR =
+ "android.sensor.wake_up_step_detector";
+
+ /**
+ * A constant describing a wake up variant of a step counter sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_STEP_COUNTER
+ */
+ public static final int TYPE_WAKE_UP_STEP_COUNTER = 38;
+
+ /**
+ * A constant string describing a wake up step counter sensor type.
+ *
+ * @see #TYPE_WAKE_UP_STEP_COUNTER
+ */
+ public static final String STRING_TYPE_WAKE_UP_STEP_COUNTER =
+ "android.sensor.wake_up_step_counter";
+
+ /**
+ * A constant describing a wake up variant of a geomagnetic rotation vector sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_GEOMAGNETIC_ROTATION_VECTOR
+ */
+ public static final int TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR = 39;
+
+ /**
+ * A constant string describing a wake up geomagnetic rotation vector sensor type.
+ *
+ * @see #TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR
+ */
+ public static final String STRING_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR =
+ "android.sensor.wake_up_geomagnetic_rotation_vector";
+
+ /**
+ * A constant describing a wake up variant of a heart rate sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_HEART_RATE
+ */
+ public static final int TYPE_WAKE_UP_HEART_RATE = 40;
+
+ /**
+ * A constant string describing a wake up heart rate sensor type.
+ *
+ * @see #TYPE_WAKE_UP_HEART_RATE
+ */
+ public static final String STRING_TYPE_WAKE_UP_HEART_RATE = "android.sensor.wake_up_heart_rate";
+
+ /**
+ * A sensor of this type generates an event each time a tilt event is detected. A tilt event
+ * is generated if the direction of the 2-seconds window average gravity changed by at
+ * least 35 degrees since the activation of the sensor. It is a wake up sensor.
+ *
+ * @see #isWakeUpSensor()
+ */
+ public static final int TYPE_WAKE_UP_TILT_DETECTOR = 41;
+
+ /**
+ * A constant string describing a wake up tilt detector sensor type.
+ *
+ * @see #TYPE_WAKE_UP_TILT_DETECTOR
+ */
+ public static final String SENSOR_STRING_TYPE_WAKE_UP_TILT_DETECTOR =
+ "android.sensor.wake_up_tilt_detector";
+
+ /**
+ * A constant describing a wake gesture sensor.
+ * <p>
+ * Wake gesture sensors enable waking up the device based on a device specific motion.
+ * <p>
+ * When this sensor triggers, the device behaves as if the power button was pressed, turning the
+ * screen on. This behavior (turning on the screen when this sensor triggers) might be
+ * deactivated by the user in the device settings. Changes in settings do not impact the
+ * behavior of the sensor: only whether the framework turns the screen on when it triggers.
+ * <p>
+ * The actual gesture to be detected is not specified, and can be chosen by the manufacturer of
+ * the device. This sensor must be low power, as it is likely to be activated 24/7.
+ * Values of events created by this sensors should not be used.
+ *
+ * @see #isWakeUpSensor()
+ * @hide This sensor is expected to only be used by the power manager
+ */
+ public static final int TYPE_WAKE_GESTURE = 42;
+
+ /**
+ * A constant string describing a wake gesture sensor.
+ *
+ * @hide This sensor is expected to only be used by the power manager
+ * @see #TYPE_WAKE_GESTURE
+ */
+ public static final String STRING_TYPE_WAKE_GESTURE = "android.sensor.wake_gesture";
+
+ /**
* A constant describing all sensor types.
*/
public static final int TYPE_ALL = -1;
@@ -441,7 +792,29 @@ public final class Sensor {
REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_STEP_DETECTOR
REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_STEP_COUNTER
REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR
- REPORTING_MODE_ON_CHANGE, 1 // SENSOR_TYPE_HEART_RATE_MONITOR
+ REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_HEART_RATE_MONITOR
+ REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR
+ // wake up variants of base sensors
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_ACCELEROMETER
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_ORIENTATION
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_GYROSCOPE
+ REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_WAKE_UP_LIGHT
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_PRESSURE
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_GRAVITY
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_LINEAR_ACCELERATION
+ REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_WAKE_UP_ROTATION_VECTOR
+ REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_WAKE_UP_RELATIVE_HUMIDITY
+ REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_WAKE_UP_AMBIENT_TEMPERATURE
+ REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED
+ REPORTING_MODE_CONTINUOUS, 4, // SENSOR_TYPE_WAKE_UP_GAME_ROTATION_VECTOR
+ REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED
+ REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_STEP_DETECTOR
+ REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_STEP_COUNTER
+ REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR
+ REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_HEART_RATE_MONITOR
+ REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_TILT_DETECTOR
+ REPORTING_MODE_ONE_SHOT, 1, // SENSOR_TYPE_WAKE_GESTURE
};
static int getReportingMode(Sensor sensor) {
@@ -499,6 +872,8 @@ public final class Sensor {
private int mFifoMaxEventCount;
private String mStringType;
private String mRequiredPermission;
+ private int mMaxDelay;
+ private boolean mWakeUpSensor;
Sensor() {
}
@@ -587,6 +962,7 @@ public final class Sensor {
}
/**
+ * @hide
* @return The permission required to access this sensor. If empty, no permission is required.
*/
public String getRequiredPermission() {
@@ -598,6 +974,51 @@ public final class Sensor {
return mHandle;
}
+ /**
+ * This value is defined only for continuous mode sensors. It is the delay between two
+ * sensor events corresponding to the lowest frequency that this sensor supports. When
+ * lower frequencies are requested through registerListener() the events will be generated
+ * at this frequency instead. It can be used to estimate when the batch FIFO may be full.
+ * Older devices may set this value to zero. Ignore this value in case it is negative
+ * or zero.
+ *
+ * @return The max delay for this sensor in microseconds.
+ */
+ public int getMaxDelay() {
+ return mMaxDelay;
+ }
+
+ /**
+ * Returns whether this sensor is a wake-up sensor.
+ * <p>
+ * Wake up sensors wake the application processor up when they have events to deliver. When a
+ * wake up sensor is registered to without batching enabled, each event will wake the
+ * application processor up.
+ * <p>
+ * When a wake up sensor is registered to with batching enabled, it
+ * wakes the application processor up when maxReportingLatency has elapsed or when the hardware
+ * FIFO storing the events from wake up sensors is getting full.
+ * <p>
+ * Non-wake up sensors never wake the application processor up. Their events are only reported
+ * when the application processor is awake, for example because the application holds a wake
+ * lock, or another source woke the application processor up.
+ * <p>
+ * When a non-wake up sensor is registered to without batching enabled, the measurements made
+ * while the application processor is asleep might be lost and never returned.
+ * <p>
+ * When a non-wake up sensor is registered to with batching enabled, the measurements made while
+ * the application processor is asleep are stored in the hardware FIFO for non-wake up sensors.
+ * When this FIFO gets full, new events start overwriting older events. When the application
+ * then wakes up, the latest events are returned, and some old events might be lost. The number
+ * of events actually returned depends on the hardware FIFO size, as well as on what other
+ * sensors are activated. If losing sensor events is not acceptable during batching, you must
+ * use the wake-up version of the sensor.
+ * @return true if this is a wake up sensor, false otherwise.
+ */
+ public boolean isWakeUpSensor() {
+ return mWakeUpSensor;
+ }
+
void setRange(float max, float res) {
mMaxRange = max;
mResolution = res;
diff --git a/core/java/android/hardware/SensorEventListener.java b/core/java/android/hardware/SensorEventListener.java
index 677d244..0d859fb 100644
--- a/core/java/android/hardware/SensorEventListener.java
+++ b/core/java/android/hardware/SensorEventListener.java
@@ -39,11 +39,13 @@ public interface SensorEventListener {
public void onSensorChanged(SensorEvent event);
/**
- * Called when the accuracy of a sensor has changed.
- * <p>See {@link android.hardware.SensorManager SensorManager}
- * for details.
+ * Called when the accuracy of the registered sensor has changed.
+ *
+ * <p>See the SENSOR_STATUS_* constants in
+ * {@link android.hardware.SensorManager SensorManager} for details.
*
- * @param accuracy The new accuracy of this sensor
+ * @param accuracy The new accuracy of this sensor, one of
+ * {@code SensorManager.SENSOR_STATUS_*}
*/
- public void onAccuracyChanged(Sensor sensor, int accuracy);
+ public void onAccuracyChanged(Sensor sensor, int accuracy);
}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 5f2b5f0..25c7630 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -321,6 +321,13 @@ public abstract class SensorManager {
/**
+ * The values returned by this sensor cannot be trusted because the sensor
+ * had no contact with what it was measuring (for example, the heart rate
+ * monitor is not in contact with the user).
+ */
+ public static final int SENSOR_STATUS_NO_CONTACT = -1;
+
+ /**
* The values returned by this sensor cannot be trusted, calibration is
* needed or the environment doesn't allow readings
*/
@@ -421,9 +428,10 @@ public abstract class SensorManager {
* {@link SensorManager#getSensorList(int) getSensorList}.
*
* @param type
- * of sensors requested
+ * of sensors requested
*
- * @return the default sensors matching the asked type.
+ * @return the default sensor matching the requested type if one exists and the application
+ * has the necessary permissions, or null otherwise.
*
* @see #getSensorList(int)
* @see Sensor
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 8684a04..b66ec86 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -395,25 +395,12 @@ public class SystemSensorManager extends SensorManager {
t.timestamp = timestamp;
t.accuracy = inAccuracy;
t.sensor = sensor;
- switch (t.sensor.getType()) {
- // Only report accuracy for sensors that support it.
- case Sensor.TYPE_MAGNETIC_FIELD:
- case Sensor.TYPE_ORIENTATION:
- // call onAccuracyChanged() only if the value changes
- final int accuracy = mSensorAccuracies.get(handle);
- if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
- mSensorAccuracies.put(handle, t.accuracy);
- mListener.onAccuracyChanged(t.sensor, t.accuracy);
- }
- break;
- default:
- // For other sensors, just report the accuracy once
- if (mFirstEvent.get(handle) == false) {
- mFirstEvent.put(handle, true);
- mListener.onAccuracyChanged(
- t.sensor, SENSOR_STATUS_ACCURACY_HIGH);
- }
- break;
+
+ // call onAccuracyChanged() only if the value changes
+ final int accuracy = mSensorAccuracies.get(handle);
+ if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
+ mSensorAccuracies.put(handle, t.accuracy);
+ mListener.onAccuracyChanged(t.sensor, t.accuracy);
}
mListener.onSensorChanged(t);
}
diff --git a/core/java/android/hardware/camera2/CameraAccessException.java b/core/java/android/hardware/camera2/CameraAccessException.java
index 1af575f..ca71e81 100644
--- a/core/java/android/hardware/camera2/CameraAccessException.java
+++ b/core/java/android/hardware/camera2/CameraAccessException.java
@@ -114,7 +114,10 @@ public class CameraAccessException extends AndroidException {
mReason = problem;
}
- private static String getDefaultMessage(int problem) {
+ /**
+ * @hide
+ */
+ public static String getDefaultMessage(int problem) {
switch (problem) {
case CAMERA_IN_USE:
return "The camera device is in use already";
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
new file mode 100644
index 0000000..7738d2d
--- /dev/null
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -0,0 +1,669 @@
+/*
+ * 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.Handler;
+import java.util.List;
+
+/**
+ * A configured capture session for a {@link CameraDevice}, used for capturing
+ * images from the camera.
+ *
+ * <p>A CameraCaptureSession is created by providing a set of target output surfaces to
+ * {@link CameraDevice#createCaptureSession createCaptureSession}. Once created, the session is
+ * active until a new session is created by the camera device, or the camera device is closed.</p>
+ *
+ * <p>Creating a session is an expensive operation and can take several hundred milliseconds, since
+ * it requires configuring the camera device's internal pipelines and allocating memory buffers for
+ * sending images to the desired targets. While
+ * {@link CameraDevice#createCaptureSession createCaptureSession} will provide a
+ * CameraCaptureSession object immediately, configuration won't be complete until the
+ * {@link CameraCaptureSession.StateListener#onConfigured onConfigured} callback is called for the
+ * first time. If configuration cannot be completed, then the
+ * {@link CameraCaptureSession.StateListener#onConfigureFailed onConfigureFailed} is called, and the
+ * session will not become active.</p>
+ *
+ * <p>Any capture requests (repeating or non-repeating) submitted before the session is ready will
+ * be queued up and will begin capture once the session becomes ready. In case the session cannot be
+ * configured and {@link StateListener#onConfigureFailed onConfigureFailed} is called, all queued
+ * capture requests are discarded.</p>
+ *
+ * <p>If a new session is created by the camera device, then the previous session is closed, and its
+ * associated {@link StateListener#onClosed onClosed} callback will be invoked. All
+ * of the session methods will throw an IllegalStateException if called once the session is
+ * closed.</p>
+ *
+ * <p>A closed session clears any repeating requests (as if {@link #stopRepeating} had been called),
+ * but will still complete all of its in-progress capture requests as normal, before a newly
+ * created session takes over and reconfigures the camera device.</p>
+ */
+public abstract class CameraCaptureSession implements AutoCloseable {
+
+ /**
+ * Get the camera device that this session is created for
+ */
+ public abstract CameraDevice getDevice();
+
+ /**
+ * <p>Submit a request for an image to be captured by the camera device.</p>
+ *
+ * <p>The request defines all the parameters for capturing the single image,
+ * including sensor, lens, flash, and post-processing settings.</p>
+ *
+ * <p>Each request will produce one {@link CaptureResult} and produce new frames for one or more
+ * target Surfaces, set with the CaptureRequest builder's
+ * {@link CaptureRequest.Builder#addTarget} method. The target surfaces (set with
+ * {@link CaptureRequest.Builder#addTarget}) must be a subset of the surfaces provided when this
+ * capture session was created.</p>
+ *
+ * <p>Multiple requests can be in progress at once. They are processed in
+ * first-in, first-out order, with minimal delays between each
+ * capture. Requests submitted through this method have higher priority than
+ * those submitted through {@link #setRepeatingRequest} or
+ * {@link #setRepeatingBurst}, and will be processed as soon as the current
+ * repeat/repeatBurst processing completes.</p>
+ *
+ * @param request the settings for this capture
+ * @param listener The callback object to notify once this request has been
+ * processed. If null, no metadata will be produced for this capture,
+ * although image data will still be produced.
+ * @param handler the handler on which the listener should be invoked, or
+ * {@code null} to use the current thread's {@link android.os.Looper
+ * looper}.
+ *
+ * @return int A unique capture sequence ID used by
+ * {@link CaptureListener#onCaptureSequenceCompleted}.
+ *
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if this session is no longer active, either because a new
+ * session has been created or the camera device has been closed.
+ * @throws IllegalArgumentException if the request targets Surfaces that are not configured as
+ * outputs for this session. Or if the handler is null, the
+ * listener is not null, and the calling thread has no looper.
+ *
+ * @see #captureBurst
+ * @see #setRepeatingRequest
+ * @see #setRepeatingBurst
+ */
+ public abstract int capture(CaptureRequest request, CaptureListener listener, Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * Submit a list of requests to be captured in sequence as a burst. The
+ * burst will be captured in the minimum amount of time possible, and will
+ * not be interleaved with requests submitted by other capture or repeat
+ * calls.
+ *
+ * <p>The requests will be captured in order, each capture producing one {@link CaptureResult}
+ * and image buffers for one or more target {@link android.view.Surface surfaces}. The target
+ * surfaces (set with {@link CaptureRequest.Builder#addTarget}) must be a subset of the surfaces
+ * provided when this capture session was created.</p>
+ *
+ * <p>The main difference between this method and simply calling
+ * {@link #capture} repeatedly is that this method guarantees that no
+ * other requests will be interspersed with the burst.</p>
+ *
+ * @param requests the list of settings for this burst capture
+ * @param listener The callback object to notify each time one of the
+ * requests in the burst has been processed. If null, no metadata will be
+ * produced for any requests in this burst, although image data will still
+ * be produced.
+ * @param handler the handler on which the listener should be invoked, or
+ * {@code null} to use the current thread's {@link android.os.Looper
+ * looper}.
+ *
+ * @return int A unique capture sequence ID used by
+ * {@link CaptureListener#onCaptureSequenceCompleted}.
+ *
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if this session is no longer active, either because a new
+ * session has been created or the camera device has been closed.
+ * @throws IllegalArgumentException If the requests target Surfaces not currently configured as
+ * outputs. Or if the handler is null, the listener is not
+ * null, and the calling thread has no looper.
+ *
+ * @see #capture
+ * @see #setRepeatingRequest
+ * @see #setRepeatingBurst
+ */
+ public abstract int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
+ Handler handler) throws CameraAccessException;
+
+ /**
+ * Request endlessly repeating capture of images by this capture session.
+ *
+ * <p>With this method, the camera device will continually capture images
+ * using the settings in the provided {@link CaptureRequest}, at the maximum
+ * rate possible.</p>
+ *
+ * <p>Repeating requests are a simple way for an application to maintain a
+ * preview or other continuous stream of frames, without having to
+ * continually submit identical requests through {@link #capture}.</p>
+ *
+ * <p>Repeat requests have lower priority than those submitted
+ * through {@link #capture} or {@link #captureBurst}, so if
+ * {@link #capture} is called when a repeating request is active, the
+ * capture request will be processed before any further repeating
+ * requests are processed.<p>
+ *
+ * <p>Repeating requests are a simple way for an application to maintain a
+ * preview or other continuous stream of frames, without having to submit
+ * requests through {@link #capture} at video rates.</p>
+ *
+ * <p>To stop the repeating capture, call {@link #stopRepeating}. Calling
+ * {@link #abortCaptures} will also clear the request.</p>
+ *
+ * <p>Calling this method will replace any earlier repeating request or
+ * burst set up by this method or {@link #setRepeatingBurst}, although any
+ * in-progress burst will be completed before the new repeat request will be
+ * used.</p>
+ *
+ * @param request the request to repeat indefinitely
+ * @param listener The callback object to notify every time the
+ * request finishes processing. If null, no metadata will be
+ * produced for this stream of requests, although image data will
+ * still be produced.
+ * @param handler the handler on which the listener should be invoked, or
+ * {@code null} to use the current thread's {@link android.os.Looper
+ * looper}.
+ *
+ * @return int A unique capture sequence ID used by
+ * {@link CaptureListener#onCaptureSequenceCompleted}.
+ *
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if this session is no longer active, either because a new
+ * session has been created or the camera device has been closed.
+ * @throws IllegalArgumentException If the requests reference Surfaces that are not currently
+ * configured as outputs. Or if the handler is null, the
+ * listener is not null, and the calling thread has no looper.
+ *
+ * @see #capture
+ * @see #captureBurst
+ * @see #setRepeatingBurst
+ * @see #stopRepeating
+ * @see #abortCaptures
+ */
+ public abstract int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
+ Handler handler) throws CameraAccessException;
+
+ /**
+ * <p>Request endlessly repeating capture of a sequence of images by this
+ * capture session.</p>
+ *
+ * <p>With this method, the camera device will continually capture images,
+ * cycling through the settings in the provided list of
+ * {@link CaptureRequest CaptureRequests}, at the maximum rate possible.</p>
+ *
+ * <p>If a request is submitted through {@link #capture} or
+ * {@link #captureBurst}, the current repetition of the request list will be
+ * completed before the higher-priority request is handled. This guarantees
+ * that the application always receives a complete repeat burst captured in
+ * minimal time, instead of bursts interleaved with higher-priority
+ * captures, or incomplete captures.</p>
+ *
+ * <p>Repeating burst requests are a simple way for an application to
+ * maintain a preview or other continuous stream of frames where each
+ * request is different in a predicatable way, without having to continually
+ * submit requests through {@link #captureBurst}.</p>
+ *
+ * <p>To stop the repeating capture, call {@link #stopRepeating}. Any
+ * ongoing burst will still be completed, however. Calling
+ * {@link #abortCaptures} will also clear the request.</p>
+ *
+ * <p>Calling this method will replace a previously-set repeating request or
+ * burst set up by this method or {@link #setRepeatingRequest}, although any
+ * in-progress burst will be completed before the new repeat burst will be
+ * used.</p>
+ *
+ * @param requests the list of requests to cycle through indefinitely
+ * @param listener The callback object to notify each time one of the
+ * requests in the repeating bursts has finished processing. If null, no
+ * metadata will be produced for this stream of requests, although image
+ * data will still be produced.
+ * @param handler the handler on which the listener should be invoked, or
+ * {@code null} to use the current thread's {@link android.os.Looper
+ * looper}.
+ *
+ * @return int A unique capture sequence ID used by
+ * {@link CaptureListener#onCaptureSequenceCompleted}.
+ *
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if this session is no longer active, either because a new
+ * session has been created or the camera device has been closed.
+ * @throws IllegalArgumentException If the requests reference Surfaces not currently configured
+ * as outputs. Or if the handler is null, the listener is not
+ * null, and the calling thread has no looper.
+ *
+ * @see #capture
+ * @see #captureBurst
+ * @see #setRepeatingRequest
+ * @see #stopRepeating
+ * @see #abortCaptures
+ */
+ public abstract int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
+ Handler handler) throws CameraAccessException;
+
+ /**
+ * <p>Cancel any ongoing repeating capture set by either
+ * {@link #setRepeatingRequest setRepeatingRequest} or
+ * {@link #setRepeatingBurst}. Has no effect on requests submitted through
+ * {@link #capture capture} or {@link #captureBurst captureBurst}.</p>
+ *
+ * <p>Any currently in-flight captures will still complete, as will any burst that is
+ * mid-capture. To ensure that the device has finished processing all of its capture requests
+ * and is in ready state, wait for the {@link StateListener#onReady} callback after
+ * calling this method.</p>
+ *
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if this session is no longer active, either because a new
+ * session has been created or the camera device has been closed.
+ *
+ * @see #setRepeatingRequest
+ * @see #setRepeatingBurst
+ * @see StateListener#onIdle
+ */
+ public abstract void stopRepeating() throws CameraAccessException;
+
+ /**
+ * Discard all captures currently pending and in-progress as fast as possible.
+ *
+ * <p>The camera device will discard all of its current work as fast as possible. Some in-flight
+ * captures may complete successfully and call {@link CaptureListener#onCaptureCompleted}, while
+ * others will trigger their {@link CaptureListener#onCaptureFailed} callbacks. If a repeating
+ * request or a repeating burst is set, it will be cleared.</p>
+ *
+ * <p>This method is the fastest way to switch the camera device to a new session with
+ * {@link CameraDevice#createCaptureSession}, at the cost of discarding in-progress work. It
+ * must be called before the new session is created. Once all pending requests are either
+ * completed or thrown away, the {@link StateListener#onReady} callback will be called,
+ * if the session has not been closed. Otherwise, the {@link StateListener#onClosed}
+ * callback will be fired when a new session is created by the camera device.</p>
+ *
+ * <p>Cancelling will introduce at least a brief pause in the stream of data from the camera
+ * device, since once the camera device is emptied, the first new request has to make it through
+ * the entire camera pipeline before new output buffers are produced.</p>
+ *
+ * <p>This means that using {@code abortCaptures()} to simply remove pending requests is not
+ * recommended; it's best used for quickly switching output configurations, or for cancelling
+ * long in-progress requests (such as a multi-second capture).</p>
+ *
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if this session is no longer active, either because a new
+ * session has been created or the camera device has been closed.
+ *
+ * @see #setRepeatingRequest
+ * @see #setRepeatingBurst
+ * @see #configureOutputs
+ */
+ public abstract void abortCaptures() throws CameraAccessException;
+
+ /**
+ * Close this capture session asynchronously.
+ *
+ * <p>Closing a session frees up the target output Surfaces of the session for reuse with either a
+ * new session, or to other APIs that can draw to Surfaces.</p>
+ *
+ * <p>Note that creating a new capture session with {@link CameraDevice#createCaptureSession}
+ * will close any existing capture session automatically, and call the older session listener's
+ * {@link StateListener#onClosed} callback. Using {@link CameraDevice#createCaptureSession}
+ * directly without closing is the recommended approach for quickly switching to a new session,
+ * since unchanged target outputs can be reused more efficiently.</p>
+ *
+ * <p>Once a session is closed, all methods on it will throw an IllegalStateException, and any
+ * repeating requests or bursts are stopped (as if {@link #stopRepeating()} was called).
+ * However, any in-progress capture requests submitted to the session will be completed as
+ * normal; once all captures have completed and the session has been torn down,
+ * {@link StateListener#onClosed} will be called.</p>
+ */
+ @Override
+ public abstract void close();
+
+ /**
+ * A listener for tracking the state of a camera capture session.
+ *
+ */
+ public static abstract class StateListener {
+
+ /**
+ * This method is called when the camera device has finished configuring itself, and the
+ * session can start processing capture requests.
+ *
+ * <p>If there are capture requests already queued with the session, they will start
+ * processing once this callback is invoked, and the session will call {@link #onActive}
+ * right after this callback is invoked.</p>
+ *
+ * <p>If no capture requests have been submitted, then the session will invoke
+ * {@link #onReady} right after this callback.</p>
+ *
+ * <p>If the camera device configuration fails, then {@link #onConfigureFailed} will
+ * be invoked instead of this callback.</p>
+ *
+ */
+ public abstract void onConfigured(CameraCaptureSession session);
+
+ /**
+ * This method is called if the session cannot be configured as requested.
+ *
+ * <p>This can happen if the set of requested outputs contains unsupported sizes,
+ * or too many outputs are requested at once.</p>
+ *
+ * <p>The session is considered to be closed, and all methods called on it after this
+ * callback is invoked will throw an IllegalStateException. Any capture requests submitted
+ * to the session prior to this callback will be discarded and will not produce any
+ * callbacks on their listeners.</p>
+ */
+ public abstract void onConfigureFailed(CameraCaptureSession session);
+
+ /**
+ * This method is called every time the session has no more capture requests to process.
+ *
+ * <p>During the creation of a new session, this callback is invoked right after
+ * {@link #onConfigured} if no capture requests were submitted to the session prior to it
+ * completing configuration.</p>
+ *
+ * <p>Otherwise, this callback will be invoked any time the session finishes processing
+ * all of its active capture requests, and no repeating request or burst is set up.</p>
+ *
+ */
+ public void onReady(CameraCaptureSession session) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called when the session starts actively processing capture requests.
+ *
+ * <p>If capture requests are submitted prior to {@link #onConfigured} being called,
+ * then the session will start processing those requests immediately after the callback,
+ * and this method will be immediately called after {@link #onConfigured}.
+ *
+ * <p>If the session runs out of capture requests to process and calls {@link #onReady},
+ * then this callback will be invoked again once new requests are submitted for capture.</p>
+ */
+ public void onActive(CameraCaptureSession session) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called when the session is closed.
+ *
+ * <p>A session is closed when a new session is created by the parent camera device,
+ * or when the parent camera device is closed (either by the user closing the device,
+ * or due to a camera device disconnection or fatal error).</p>
+ *
+ * <p>Once a session is closed, all methods on it will throw an IllegalStateException, and
+ * any repeating requests or bursts are stopped (as if {@link #stopRepeating()} was called).
+ * However, any in-progress capture requests submitted to the session will be completed
+ * as normal.</p>
+ */
+ public void onClosed(CameraCaptureSession session) {
+ // default empty implementation
+ }
+ }
+
+ /**
+ * <p>A listener for tracking the progress of a {@link CaptureRequest}
+ * submitted to the camera device.</p>
+ *
+ * <p>This listener is called when a request triggers a capture to start,
+ * and when the capture is complete. In case on an error capturing an image,
+ * the error method is triggered instead of the completion method.</p>
+ *
+ * @see #capture
+ * @see #captureBurst
+ * @see #setRepeatingRequest
+ * @see #setRepeatingBurst
+ */
+ 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.
+ *
+ * <p>This callback is invoked right as the capture of a frame begins,
+ * so it is the most appropriate time for playing a shutter sound,
+ * or triggering UI indicators of capture.</p>
+ *
+ * <p>The request that is being used for this capture is provided, along
+ * with the actual timestamp for the start of exposure. This timestamp
+ * matches the timestamp that will be included in
+ * {@link CaptureResult#SENSOR_TIMESTAMP the result timestamp field},
+ * and in the buffers sent to each output Surface. These buffer
+ * timestamps are accessible through, for example,
+ * {@link android.media.Image#getTimestamp() Image.getTimestamp()} or
+ * {@link android.graphics.SurfaceTexture#getTimestamp()}.</p>
+ *
+ * <p>For the simplest way to play a shutter sound camera shutter or a
+ * video recording start/stop sound, see the
+ * {@link android.media.MediaActionSound} class.</p>
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param camera the CameraDevice sending the callback
+ * @param request the request for the capture that just begun
+ * @param timestamp the timestamp at start of capture, in nanoseconds.
+ *
+ * @see android.media.MediaActionSound
+ */
+ public void onCaptureStarted(CameraDevice camera,
+ CaptureRequest request, long timestamp) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called when some results from an image capture are
+ * available.
+ *
+ * <p>The result provided here will contain some subset of the fields of
+ * a full result. Multiple onCapturePartial calls may happen per
+ * capture; a given result field will only be present in one partial
+ * capture at most. The final onCaptureCompleted call will always
+ * contain all the fields, whether onCapturePartial was called or
+ * not.</p>
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param camera The CameraDevice sending the callback.
+ * @param request The request that was given to the CameraDevice
+ * @param result The partial output metadata from the capture, which
+ * includes a subset of the CaptureResult fields.
+ *
+ * @see #capture
+ * @see #captureBurst
+ * @see #setRepeatingRequest
+ * @see #setRepeatingBurst
+ *
+ * @hide
+ */
+ public void onCapturePartial(CameraDevice camera,
+ CaptureRequest request, CaptureResult result) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called when an image capture makes partial forward progress; some
+ * (but not all) results from an image capture are available.
+ *
+ * <p>The result provided here will contain some subset of the fields of
+ * a full result. Multiple {@link #onCaptureProgressed} calls may happen per
+ * capture; a given result field will only be present in one partial
+ * capture at most. The final {@link #onCaptureCompleted} call will always
+ * contain all the fields (in particular, the union of all the fields of all
+ * the partial results composing the total result).</p>
+ *
+ * <p>For each request, some result data might be available earlier than others. The typical
+ * delay between each partial result (per request) is a single frame interval.
+ * For performance-oriented use-cases, applications should query the metadata they need
+ * to make forward progress from the partial results and avoid waiting for the completed
+ * result.</p>
+ *
+ * <p>Each request will generate at least {@code 1} partial results, and at most
+ * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT} partial results.</p>
+ *
+ * <p>Depending on the request settings, the number of partial results per request
+ * will vary, although typically the partial count could be the same as long as the
+ * camera device subsystems enabled stay the same.</p>
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param camera The CameraDevice sending the callback.
+ * @param request The request that was given to the CameraDevice
+ * @param partialResult The partial output metadata from the capture, which
+ * includes a subset of the {@link TotalCaptureResult} fields.
+ *
+ * @see #capture
+ * @see #captureBurst
+ * @see #setRepeatingRequest
+ * @see #setRepeatingBurst
+ */
+ public void onCaptureProgressed(CameraDevice camera,
+ CaptureRequest request, CaptureResult partialResult) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called when an image capture has fully completed and all the
+ * result metadata is available.
+ *
+ * <p>This callback will always fire after the last {@link #onCaptureProgressed};
+ * in other words, no more partial results will be delivered once the completed result
+ * is available.</p>
+ *
+ * <p>For performance-intensive use-cases where latency is a factor, consider
+ * using {@link #onCaptureProgressed} instead.</p>
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param camera The CameraDevice sending the callback.
+ * @param request The request that was given to the CameraDevice
+ * @param result The total output metadata from the capture, including the
+ * final capture parameters and the state of the camera system during
+ * capture.
+ *
+ * @see #capture
+ * @see #captureBurst
+ * @see #setRepeatingRequest
+ * @see #setRepeatingBurst
+ */
+ public void onCaptureCompleted(CameraDevice camera,
+ CaptureRequest request, TotalCaptureResult result) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called instead of {@link #onCaptureCompleted} when the
+ * camera device failed to produce a {@link CaptureResult} for the
+ * request.
+ *
+ * <p>Other requests are unaffected, and some or all image buffers from
+ * the capture may have been pushed to their respective output
+ * streams.</p>
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param camera
+ * The CameraDevice sending the callback.
+ * @param request
+ * The request that was given to the CameraDevice
+ * @param failure
+ * The output failure from the capture, including the failure reason
+ * and the frame number.
+ *
+ * @see #capture
+ * @see #captureBurst
+ * @see #setRepeatingRequest
+ * @see #setRepeatingBurst
+ */
+ public void onCaptureFailed(CameraDevice camera,
+ CaptureRequest request, CaptureFailure failure) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called independently of the others in CaptureListener,
+ * when a capture sequence finishes and all {@link CaptureResult}
+ * or {@link CaptureFailure} for it have been returned via this listener.
+ *
+ * <p>In total, there will be at least one result/failure returned by this listener
+ * before this callback is invoked. If the capture sequence is aborted before any
+ * requests have been processed, {@link #onCaptureSequenceAborted} is invoked instead.</p>
+ *
+ * <p>The default implementation does nothing.</p>
+ *
+ * @param camera
+ * The CameraDevice sending the callback.
+ * @param sequenceId
+ * A sequence ID returned by the {@link #capture} family of functions.
+ * @param frameNumber
+ * The last frame number (returned by {@link CaptureResult#getFrameNumber}
+ * or {@link CaptureFailure#getFrameNumber}) in the capture sequence.
+ *
+ * @see CaptureResult#getFrameNumber()
+ * @see CaptureFailure#getFrameNumber()
+ * @see CaptureResult#getSequenceId()
+ * @see CaptureFailure#getSequenceId()
+ * @see #onCaptureSequenceAborted
+ */
+ public void onCaptureSequenceCompleted(CameraDevice camera,
+ int sequenceId, long frameNumber) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called independently of the others in CaptureListener,
+ * when a capture sequence aborts before any {@link CaptureResult}
+ * or {@link CaptureFailure} for it have been returned via this listener.
+ *
+ * <p>Due to the asynchronous nature of the camera device, not all submitted captures
+ * are immediately processed. It is possible to clear out the pending requests
+ * by a variety of operations such as {@link CameraDevice#stopRepeating} or
+ * {@link CameraDevice#flush}. When such an event happens,
+ * {@link #onCaptureSequenceCompleted} will not be called.</p>
+ *
+ * <p>The default implementation does nothing.</p>
+ *
+ * @param camera
+ * The CameraDevice sending the callback.
+ * @param sequenceId
+ * A sequence ID returned by the {@link #capture} family of functions.
+ *
+ * @see CaptureResult#getFrameNumber()
+ * @see CaptureFailure#getFrameNumber()
+ * @see CaptureResult#getSequenceId()
+ * @see CaptureFailure#getSequenceId()
+ * @see #onCaptureSequenceCompleted
+ */
+ public void onCaptureSequenceAborted(CameraDevice camera,
+ int sequenceId) {
+ // default empty implementation
+ }
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 5f2af8c..2f5b4fe 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -16,7 +16,10 @@
package android.hardware.camera2;
+import android.hardware.camera2.CaptureResult.Key;
import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.utils.TypeReference;
+import android.util.Rational;
import java.util.Collections;
import java.util.List;
@@ -29,29 +32,173 @@ import java.util.List;
* through the {@link CameraManager CameraManager}
* interface in addition to through the CameraDevice interface.</p>
*
+ * <p>{@link CameraCharacteristics} objects are immutable.</p>
+ *
* @see CameraDevice
* @see CameraManager
*/
-public final class CameraCharacteristics extends CameraMetadata {
+public final class CameraCharacteristics extends CameraMetadata<CameraCharacteristics.Key<?>> {
+
+ /**
+ * A {@code Key} is used to do camera characteristics field lookups with
+ * {@link CameraCharacteristics#get}.
+ *
+ * <p>For example, to get the stream configuration map:
+ * <code><pre>
+ * StreamConfigurationMap map = cameraCharacteristics.get(
+ * CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ * </pre></code>
+ * </p>
+ *
+ * <p>To enumerate over all possible keys for {@link CameraCharacteristics}, see
+ * {@link CameraCharacteristics#getKeys()}.</p>
+ *
+ * @see CameraCharacteristics#get
+ * @see CameraCharacteristics#getKeys()
+ */
+ public static final class Key<T> {
+ private final CameraMetadataNative.Key<T> mKey;
+
+ /**
+ * Visible for testing and vendor extensions only.
+ *
+ * @hide
+ */
+ public Key(String name, Class<T> type) {
+ mKey = new CameraMetadataNative.Key<T>(name, type);
+ }
+
+ /**
+ * Visible for testing and vendor extensions only.
+ *
+ * @hide
+ */
+ public Key(String name, TypeReference<T> typeReference) {
+ mKey = new CameraMetadataNative.Key<T>(name, typeReference);
+ }
+
+ /**
+ * Return a camelCase, period separated name formatted like:
+ * {@code "root.section[.subsections].name"}.
+ *
+ * <p>Built-in keys exposed by the Android SDK are always prefixed with {@code "android."};
+ * keys that are device/platform-specific are prefixed with {@code "com."}.</p>
+ *
+ * <p>For example, {@code CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP} would
+ * have a name of {@code "android.scaler.streamConfigurationMap"}; whereas a device
+ * specific key might look like {@code "com.google.nexus.data.private"}.</p>
+ *
+ * @return String representation of the key name
+ */
+ public String getName() {
+ return mKey.getName();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final int hashCode() {
+ return mKey.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public final boolean equals(Object o) {
+ return o instanceof Key && ((Key<T>)o).mKey.equals(mKey);
+ }
+
+ /**
+ * Visible for CameraMetadataNative implementation only; do not use.
+ *
+ * TODO: Make this private or remove it altogether.
+ *
+ * @hide
+ */
+ public CameraMetadataNative.Key<T> getNativeKey() {
+ return mKey;
+ }
+
+ @SuppressWarnings({
+ "unused", "unchecked"
+ })
+ private Key(CameraMetadataNative.Key<?> nativeKey) {
+ mKey = (CameraMetadataNative.Key<T>) nativeKey;
+ }
+ }
private final CameraMetadataNative mProperties;
- private List<Key<?>> mAvailableRequestKeys;
- private List<Key<?>> mAvailableResultKeys;
+ private List<CaptureRequest.Key<?>> mAvailableRequestKeys;
+ private List<CaptureResult.Key<?>> mAvailableResultKeys;
/**
* Takes ownership of the passed-in properties object
* @hide
*/
public CameraCharacteristics(CameraMetadataNative properties) {
- mProperties = properties;
+ mProperties = CameraMetadataNative.move(properties);
}
- @Override
+ /**
+ * Returns a copy of the underlying {@link CameraMetadataNative}.
+ * @hide
+ */
+ public CameraMetadataNative getNativeCopy() {
+ return new CameraMetadataNative(mProperties);
+ }
+
+ /**
+ * Get a camera characteristics field value.
+ *
+ * <p>The field definitions can be
+ * found in {@link CameraCharacteristics}.</p>
+ *
+ * <p>Querying the value for the same key more than once will return a value
+ * which is equal to the previous queried value.</p>
+ *
+ * @throws IllegalArgumentException if the key was not valid
+ *
+ * @param key The characteristics field to read.
+ * @return The value of that key, or {@code null} if the field is not set.
+ */
public <T> T get(Key<T> key) {
return mProperties.get(key);
}
/**
+ * {@inheritDoc}
+ * @hide
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ protected <T> T getProtected(Key<?> key) {
+ return (T) mProperties.get(key);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ protected Class<Key<?>> getKeyClass() {
+ Object thisClass = Key.class;
+ return (Class<Key<?>>)thisClass;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Key<?>> getKeys() {
+ // Force the javadoc for this function to show up on the CameraCharacteristics page
+ return super.getKeys();
+ }
+
+ /**
* Returns the list of keys supported by this {@link CameraDevice} for querying
* with a {@link CaptureRequest}.
*
@@ -65,9 +212,14 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @return List of keys supported by this CameraDevice for CaptureRequests.
*/
- public List<Key<?>> getAvailableCaptureRequestKeys() {
+ @SuppressWarnings({"unchecked"})
+ public List<CaptureRequest.Key<?>> getAvailableCaptureRequestKeys() {
if (mAvailableRequestKeys == null) {
- mAvailableRequestKeys = getAvailableKeyList(CaptureRequest.class);
+ Object crKey = CaptureRequest.Key.class;
+ Class<CaptureRequest.Key<?>> crKeyTyped = (Class<CaptureRequest.Key<?>>)crKey;
+
+ mAvailableRequestKeys = Collections.unmodifiableList(
+ getAvailableKeyList(CaptureRequest.class, crKeyTyped));
}
return mAvailableRequestKeys;
}
@@ -86,9 +238,14 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @return List of keys supported by this CameraDevice for CaptureResults.
*/
- public List<Key<?>> getAvailableCaptureResultKeys() {
+ @SuppressWarnings({"unchecked"})
+ public List<CaptureResult.Key<?>> getAvailableCaptureResultKeys() {
if (mAvailableResultKeys == null) {
- mAvailableResultKeys = getAvailableKeyList(CaptureResult.class);
+ Object crKey = CaptureResult.Key.class;
+ Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>)crKey;
+
+ mAvailableResultKeys = Collections.unmodifiableList(
+ getAvailableKeyList(CaptureResult.class, crKeyTyped));
}
return mAvailableResultKeys;
}
@@ -102,12 +259,14 @@ public final class CameraCharacteristics extends CameraMetadata {
* <p>Each key is only listed once in the list. The order of the keys is undefined.</p>
*
* @param metadataClass The subclass of CameraMetadata that you want to get the keys for.
+ * @param keyClass The class of the metadata key, e.g. CaptureRequest.Key.class
*
* @return List of keys supported by this CameraDevice for metadataClass.
*
* @throws IllegalArgumentException if metadataClass is not a subclass of CameraMetadata
*/
- private <T extends CameraMetadata> List<Key<?>> getAvailableKeyList(Class<T> metadataClass) {
+ private <TKey> List<TKey>
+ getAvailableKeyList(Class<?> metadataClass, Class<TKey> keyClass) {
if (metadataClass.equals(CameraMetadata.class)) {
throw new AssertionError(
@@ -117,7 +276,9 @@ public final class CameraCharacteristics extends CameraMetadata {
"metadataClass must be a subclass of CameraMetadata");
}
- return Collections.unmodifiableList(getKeysStatic(metadataClass, /*instance*/null));
+ List<TKey> staticKeyList = CameraCharacteristics.<TKey>getKeysStatic(
+ metadataClass, keyClass, /*instance*/null);
+ return Collections.unmodifiableList(staticKeyList);
}
/*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
@@ -135,8 +296,8 @@ public final class CameraCharacteristics extends CameraMetadata {
* 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);
+ public static final Key<int[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES =
+ new Key<int[]>("android.control.aeAvailableAntibandingModes", int[].class);
/**
* <p>The set of auto-exposure modes that are supported by this
@@ -154,15 +315,15 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @see CaptureRequest#CONTROL_AE_MODE
*/
- public static final Key<byte[]> CONTROL_AE_AVAILABLE_MODES =
- new Key<byte[]>("android.control.aeAvailableModes", byte[].class);
+ public static final Key<int[]> CONTROL_AE_AVAILABLE_MODES =
+ new Key<int[]>("android.control.aeAvailableModes", int[].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);
+ public static final Key<android.util.Range<Integer>[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES =
+ new Key<android.util.Range<Integer>[]>("android.control.aeAvailableTargetFpsRanges", new TypeReference<android.util.Range<Integer>[]>() {{ }});
/**
* <p>Maximum and minimum exposure compensation
@@ -171,8 +332,8 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @see CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP
*/
- public static final Key<int[]> CONTROL_AE_COMPENSATION_RANGE =
- new Key<int[]>("android.control.aeCompensationRange", int[].class);
+ public static final Key<android.util.Range<Integer>> CONTROL_AE_COMPENSATION_RANGE =
+ new Key<android.util.Range<Integer>>("android.control.aeCompensationRange", new TypeReference<android.util.Range<Integer>>() {{ }});
/**
* <p>Smallest step by which exposure compensation
@@ -194,8 +355,8 @@ public final class CameraCharacteristics extends CameraMetadata {
* @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);
+ public static final Key<int[]> CONTROL_AF_AVAILABLE_MODES =
+ new Key<int[]>("android.control.afAvailableModes", int[].class);
/**
* <p>List containing the subset of color effects
@@ -213,8 +374,8 @@ public final class CameraCharacteristics extends CameraMetadata {
* @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);
+ public static final Key<int[]> CONTROL_AVAILABLE_EFFECTS =
+ new Key<int[]>("android.control.availableEffects", int[].class);
/**
* <p>List containing a subset of scene modes
@@ -227,15 +388,15 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
- public static final Key<byte[]> CONTROL_AVAILABLE_SCENE_MODES =
- new Key<byte[]>("android.control.availableSceneModes", byte[].class);
+ public static final Key<int[]> CONTROL_AVAILABLE_SCENE_MODES =
+ new Key<int[]>("android.control.availableSceneModes", int[].class);
/**
* <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);
+ public static final Key<int[]> CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES =
+ new Key<int[]>("android.control.availableVideoStabilizationModes", int[].class);
/**
* <p>The set of auto-white-balance modes ({@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode})
@@ -253,8 +414,8 @@ public final class CameraCharacteristics extends CameraMetadata {
* @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);
+ public static final Key<int[]> CONTROL_AWB_AVAILABLE_MODES =
+ new Key<int[]>("android.control.awbAvailableModes", int[].class);
/**
* <p>List of the maximum number of regions that can be used for metering in
@@ -266,19 +427,53 @@ public final class CameraCharacteristics extends CameraMetadata {
* @see CaptureRequest#CONTROL_AE_REGIONS
* @see CaptureRequest#CONTROL_AF_REGIONS
* @see CaptureRequest#CONTROL_AWB_REGIONS
+ * @hide
*/
public static final Key<int[]> CONTROL_MAX_REGIONS =
new Key<int[]>("android.control.maxRegions", int[].class);
/**
+ * <p>List of the maximum number of regions that can be used for metering in
+ * auto-exposure (AE);
+ * this corresponds to the the maximum number of elements in
+ * {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_REGIONS
+ */
+ public static final Key<Integer> CONTROL_MAX_REGIONS_AE =
+ new Key<Integer>("android.control.maxRegionsAe", int.class);
+
+ /**
+ * <p>List of the maximum number of regions that can be used for metering in
+ * auto-white balance (AWB);
+ * this corresponds to the the maximum number of elements in
+ * {@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}.</p>
+ *
+ * @see CaptureRequest#CONTROL_AWB_REGIONS
+ */
+ public static final Key<Integer> CONTROL_MAX_REGIONS_AWB =
+ new Key<Integer>("android.control.maxRegionsAwb", int.class);
+
+ /**
+ * <p>List of the maximum number of regions that can be used for metering in
+ * auto-focus (AF);
+ * this corresponds to the the maximum number of elements in
+ * {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}.</p>
+ *
+ * @see CaptureRequest#CONTROL_AF_REGIONS
+ */
+ public static final Key<Integer> CONTROL_MAX_REGIONS_AF =
+ new Key<Integer>("android.control.maxRegionsAf", int.class);
+
+ /**
* <p>The set of edge enhancement modes supported by this camera device.</p>
* <p>This tag lists the valid modes for {@link CaptureRequest#EDGE_MODE android.edge.mode}.</p>
* <p>Full-capability camera devices must always support OFF and FAST.</p>
*
* @see CaptureRequest#EDGE_MODE
*/
- public static final Key<byte[]> EDGE_AVAILABLE_EDGE_MODES =
- new Key<byte[]>("android.edge.availableEdgeModes", byte[].class);
+ public static final Key<int[]> EDGE_AVAILABLE_EDGE_MODES =
+ new Key<int[]>("android.edge.availableEdgeModes", int[].class);
/**
* <p>Whether this camera device has a
@@ -297,8 +492,8 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @see CaptureRequest#HOT_PIXEL_MODE
*/
- public static final Key<byte[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES =
- new Key<byte[]>("android.hotPixel.availableHotPixelModes", byte[].class);
+ public static final Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES =
+ new Key<int[]>("android.hotPixel.availableHotPixelModes", int[].class);
/**
* <p>Supported resolutions for the JPEG thumbnail</p>
@@ -307,19 +502,17 @@ public final class CameraCharacteristics extends CameraMetadata {
* <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}.
+ * aspect ratio of largest JPEG output size in 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
+ * <li>Each output JPEG size in 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);
+ public static final Key<android.util.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES =
+ new Key<android.util.Size[]>("android.jpeg.availableThumbnailSizes", android.util.Size[].class);
/**
* <p>List of supported aperture
@@ -367,8 +560,8 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @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);
+ public static final Key<int[]> LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION =
+ new Key<int[]>("android.lens.info.availableOpticalStabilization", int[].class);
/**
* <p>Optional. Hyperfocal distance for this lens.</p>
@@ -394,9 +587,10 @@ public final class CameraCharacteristics extends CameraMetadata {
* <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>
+ * @hide
*/
- 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);
+ public static final Key<android.util.Size> LENS_INFO_SHADING_MAP_SIZE =
+ new Key<android.util.Size>("android.lens.info.shadingMapSize", android.util.Size.class);
/**
* <p>The lens focus distance calibration quality.</p>
@@ -432,8 +626,8 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @see CaptureRequest#NOISE_REDUCTION_MODE
*/
- public static final Key<byte[]> NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES =
- new Key<byte[]>("android.noiseReduction.availableNoiseReductionModes", byte[].class);
+ public static final Key<int[]> NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES =
+ new Key<int[]>("android.noiseReduction.availableNoiseReductionModes", int[].class);
/**
* <p>If set to 1, the HAL will always split result
@@ -445,8 +639,10 @@ public final class CameraCharacteristics extends CameraMetadata {
* working at that point; DO NOT USE without careful
* consideration of future support.</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ * @deprecated
* @hide
*/
+ @Deprecated
public static final Key<Byte> QUIRKS_USE_PARTIAL_RESULT =
new Key<Byte>("android.quirks.usePartialResult", byte.class);
@@ -460,9 +656,9 @@ public final class CameraCharacteristics extends CameraMetadata {
* 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
+ * CPU resources that will consume more power. The image format for an output stream can
+ * be any supported format provided by android.scaler.availableStreamConfigurations.
+ * The formats defined in 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.
@@ -471,26 +667,90 @@ public final class CameraCharacteristics extends CameraMetadata {
* <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
+ * @hide
*/
public static final Key<int[]> REQUEST_MAX_NUM_OUTPUT_STREAMS =
new Key<int[]>("android.request.maxNumOutputStreams", int[].class);
/**
+ * <p>The maximum numbers of different types of output streams
+ * that can be configured and used simultaneously by a camera device
+ * for any <code>RAW</code> formats.</p>
+ * <p>This value contains the max number of output simultaneous
+ * streams from the raw sensor.</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 this kind of an output stream can
+ * be any <code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p>
+ * <p>In particular, a <code>RAW</code> format is typically one of:</p>
+ * <ul>
+ * <li>ImageFormat#RAW_SENSOR</li>
+ * <li>Opaque <code>RAW</code></li>
+ * </ul>
+ *
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
+ */
+ public static final Key<Integer> REQUEST_MAX_NUM_OUTPUT_RAW =
+ new Key<Integer>("android.request.maxNumOutputRaw", int.class);
+
+ /**
+ * <p>The maximum numbers of different types of output streams
+ * that can be configured and used simultaneously by a camera device
+ * for any processed (but not-stalling) formats.</p>
+ * <p>This value contains the max number of output simultaneous
+ * streams for any processed (but not-stalling) formats.</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 this kind of an output stream can
+ * be any non-<code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p>
+ * <p>Processed (but not-stalling) is defined as any non-RAW format without a stall duration.
+ * Typically:</p>
+ * <ul>
+ * <li>ImageFormat#YUV_420_888</li>
+ * <li>ImageFormat#NV21</li>
+ * <li>ImageFormat#YV12</li>
+ * <li>Implementation-defined formats, i.e. StreamConfiguration#isOutputSupportedFor(Class)</li>
+ * </ul>
+ * <p>For full guarantees, query StreamConfigurationMap#getOutputStallDuration with
+ * a processed format -- it will return 0 for a non-stalling stream.</p>
+ *
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
+ */
+ public static final Key<Integer> REQUEST_MAX_NUM_OUTPUT_PROC =
+ new Key<Integer>("android.request.maxNumOutputProc", int.class);
+
+ /**
+ * <p>The maximum numbers of different types of output streams
+ * that can be configured and used simultaneously by a camera device
+ * for any processed (and stalling) formats.</p>
+ * <p>This value contains the max number of output simultaneous
+ * streams for any processed (but not-stalling) formats.</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 this kind of an output stream can
+ * be any non-<code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p>
+ * <p>A processed and stalling format is defined as any non-RAW format with a stallDurations &gt; 0.
+ * Typically only the <code>JPEG</code> format (ImageFormat#JPEG)</p>
+ * <p>For full guarantees, query StreamConfigurationMap#getOutputStallDuration with
+ * a processed format -- it will return a non-0 value for a stalling stream.</p>
+ *
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
+ */
+ public static final Key<Integer> REQUEST_MAX_NUM_OUTPUT_PROC_STALLING =
+ new Key<Integer>("android.request.maxNumOutputProcStalling", int.class);
+
+ /**
* <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
+ * 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);
@@ -550,7 +810,6 @@ public final class CameraCharacteristics extends CameraMetadata {
* {@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>
@@ -559,12 +818,12 @@ public final class CameraCharacteristics extends CameraMetadata {
* @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_MANUAL_POST_PROCESSING
* @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);
+ public static final Key<int[]> REQUEST_AVAILABLE_CAPABILITIES =
+ new Key<int[]>("android.request.availableCapabilities", int[].class);
/**
* <p>A list of all keys that the camera device has available
@@ -593,7 +852,7 @@ public final class CameraCharacteristics extends CameraMetadata {
* 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>
+ * <li>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>
@@ -604,7 +863,6 @@ public final class CameraCharacteristics extends CameraMetadata {
* <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
*/
@@ -629,22 +887,26 @@ public final class CameraCharacteristics extends CameraMetadata {
* 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>
+ * @deprecated
+ * @hide
*/
+ @Deprecated
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 {@link CameraCharacteristics#SCALER_AVAILABLE_JPEG_SIZES android.scaler.availableJpegSizes}.</p>
+ * for each resolution in 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
+ * @deprecated
+ * @hide
*/
+ @Deprecated
public static final Key<long[]> SCALER_AVAILABLE_JPEG_MIN_DURATIONS =
new Key<long[]>("android.scaler.availableJpegMinDurations", long[].class);
@@ -654,9 +916,12 @@ public final class CameraCharacteristics extends CameraMetadata {
* sensor maximum resolution (defined by {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}).</p>
*
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ * @deprecated
+ * @hide
*/
- 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);
+ @Deprecated
+ public static final Key<android.util.Size[]> SCALER_AVAILABLE_JPEG_SIZES =
+ new Key<android.util.Size[]>("android.scaler.availableJpegSizes", android.util.Size[].class);
/**
* <p>The maximum ratio between active area width
@@ -669,16 +934,17 @@ public final class CameraCharacteristics extends CameraMetadata {
/**
* <p>For each available processed output size (defined in
- * {@link CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_SIZES android.scaler.availableProcessedSizes}), this property lists the
+ * 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
+ * @deprecated
+ * @hide
*/
+ @Deprecated
public static final Key<long[]> SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS =
new Key<long[]>("android.scaler.availableProcessedMinDurations", long[].class);
@@ -696,9 +962,12 @@ public final class CameraCharacteristics extends CameraMetadata {
* 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>
+ * @deprecated
+ * @hide
*/
- 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);
+ @Deprecated
+ public static final Key<android.util.Size[]> SCALER_AVAILABLE_PROCESSED_SIZES =
+ new Key<android.util.Size[]>("android.scaler.availableProcessedSizes", android.util.Size[].class);
/**
* <p>The mapping of image formats that are supported by this
@@ -746,13 +1015,14 @@ public final class CameraCharacteristics extends CameraMetadata {
* </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>
+ * StreamConfigurationMap#getOutputStallDuration(int,Size)
+ * for a <code>format =</code> RAW_OPAQUE is always 0).</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>
+ * <p>TODO: typedef to ReprocessFormatMap</p>
*
* @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS
- * @see CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS
+ * @hide
*/
public static final Key<int[]> SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP =
new Key<int[]>("android.scaler.availableInputOutputFormatsMap", int[].class);
@@ -775,7 +1045,7 @@ public final class CameraCharacteristics extends CameraMetadata {
* 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>
+ * 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>
@@ -844,13 +1114,11 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @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
+ * @hide
*/
- public static final Key<int[]> SCALER_AVAILABLE_STREAM_CONFIGURATIONS =
- new Key<int[]>("android.scaler.availableStreamConfigurations", int[].class);
+ public static final Key<android.hardware.camera2.params.StreamConfiguration[]> SCALER_AVAILABLE_STREAM_CONFIGURATIONS =
+ new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.scaler.availableStreamConfigurations", android.hardware.camera2.params.StreamConfiguration[].class);
/**
* <p>This lists the minimum frame duration for each
@@ -863,14 +1131,16 @@ public final class CameraCharacteristics extends CameraMetadata {
* <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
+ * android.scaler.availableStallDurations for more details about
* calculating the max frame rate.</p>
+ * <p>(Keep in sync with
+ * StreamConfigurationMap#getOutputMinFrameDuration)</p>
*
- * @see CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS
* @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @hide
*/
- public static final Key<long[]> SCALER_AVAILABLE_MIN_FRAME_DURATIONS =
- new Key<long[]>("android.scaler.availableMinFrameDurations", long[].class);
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> SCALER_AVAILABLE_MIN_FRAME_DURATIONS =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.scaler.availableMinFrameDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
/**
* <p>This lists the maximum stall duration for each
@@ -929,12 +1199,124 @@ public final class CameraCharacteristics extends CameraMetadata {
* 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>
+ * <p>(Keep up to date with
+ * StreamConfigurationMap#getOutputStallDuration(int, Size) )</p>
*
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
* @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @hide
*/
- public static final Key<long[]> SCALER_AVAILABLE_STALL_DURATIONS =
- new Key<long[]>("android.scaler.availableStallDurations", long[].class);
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> SCALER_AVAILABLE_STALL_DURATIONS =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.scaler.availableStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>The available stream configurations that this
+ * camera device supports; also includes the minimum frame durations
+ * and the stall durations for each format/size combination.</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>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#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP =
+ new Key<android.hardware.camera2.params.StreamConfigurationMap>("android.scaler.streamConfigurationMap", android.hardware.camera2.params.StreamConfigurationMap.class);
+
+ /**
+ * <p>The crop type that this camera device supports.</p>
+ * <p>When passing a non-centered crop region ({@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}) to a camera
+ * device that only supports CENTER_ONLY cropping, the camera device will move the
+ * crop region to the center of the sensor active array ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize})
+ * and keep the crop region width and height unchanged. The camera device will return the
+ * final used crop region in metadata result {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}.</p>
+ * <p>Camera devices that support FREEFORM cropping will support any crop region that
+ * is inside of the active array. The camera device will apply the same crop region and
+ * return the final used crop region in capture result metadata {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}.</p>
+ * <p>FULL capability devices ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} <code>==</code> FULL) will support
+ * FREEFORM cropping.</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ * @see #SCALER_CROPPING_TYPE_CENTER_ONLY
+ * @see #SCALER_CROPPING_TYPE_FREEFORM
+ */
+ public static final Key<Integer> SCALER_CROPPING_TYPE =
+ new Key<Integer>("android.scaler.croppingType", int.class);
/**
* <p>Area of raw data which corresponds to only
@@ -948,8 +1330,8 @@ public final class CameraCharacteristics extends CameraMetadata {
/**
* <p>Range of valid sensitivities</p>
*/
- public static final Key<int[]> SENSOR_INFO_SENSITIVITY_RANGE =
- new Key<int[]>("android.sensor.info.sensitivityRange", int[].class);
+ public static final Key<android.util.Range<Integer>> SENSOR_INFO_SENSITIVITY_RANGE =
+ new Key<android.util.Range<Integer>>("android.sensor.info.sensitivityRange", new TypeReference<android.util.Range<Integer>>() {{ }});
/**
* <p>Arrangement of color filters on sensor;
@@ -970,8 +1352,8 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @see CaptureRequest#SENSOR_EXPOSURE_TIME
*/
- public static final Key<long[]> SENSOR_INFO_EXPOSURE_TIME_RANGE =
- new Key<long[]>("android.sensor.info.exposureTimeRange", long[].class);
+ public static final Key<android.util.Range<Long>> SENSOR_INFO_EXPOSURE_TIME_RANGE =
+ new Key<android.util.Range<Long>>("android.sensor.info.exposureTimeRange", new TypeReference<android.util.Range<Long>>() {{ }});
/**
* <p>Maximum possible frame duration (minimum frame
@@ -982,13 +1364,9 @@ public final class CameraCharacteristics extends CameraMetadata {
* 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>
+ * StreamConfigurationMap#getOutputMinFrameDuration(int,Size)
+ * 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 =
@@ -999,20 +1377,18 @@ public final class CameraCharacteristics extends CameraMetadata {
* 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);
+ public static final Key<android.util.SizeF> SENSOR_INFO_PHYSICAL_SIZE =
+ new Key<android.util.SizeF>("android.sensor.info.physicalSize", android.util.SizeF.class);
/**
* <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
+ * android.scaler.availableStreamConfigurations.</p>
*/
- 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);
+ public static final Key<android.util.Size> SENSOR_INFO_PIXEL_ARRAY_SIZE =
+ new Key<android.util.Size>("android.sensor.info.pixelArraySize", android.util.Size.class);
/**
* <p>Maximum raw value output by sensor.</p>
@@ -1108,8 +1484,8 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
*/
- public static final Key<Rational[]> SENSOR_CALIBRATION_TRANSFORM1 =
- new Key<Rational[]>("android.sensor.calibrationTransform1", Rational[].class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_CALIBRATION_TRANSFORM1 =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.calibrationTransform1", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <p>A per-device calibration transform matrix that maps from the
@@ -1129,8 +1505,8 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2
*/
- public static final Key<Rational[]> SENSOR_CALIBRATION_TRANSFORM2 =
- new Key<Rational[]>("android.sensor.calibrationTransform2", Rational[].class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_CALIBRATION_TRANSFORM2 =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.calibrationTransform2", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <p>A matrix that transforms color values from CIE XYZ color space to
@@ -1151,8 +1527,8 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
*/
- public static final Key<Rational[]> SENSOR_COLOR_TRANSFORM1 =
- new Key<Rational[]>("android.sensor.colorTransform1", Rational[].class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_COLOR_TRANSFORM1 =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.colorTransform1", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <p>A matrix that transforms color values from CIE XYZ color space to
@@ -1175,8 +1551,8 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2
*/
- public static final Key<Rational[]> SENSOR_COLOR_TRANSFORM2 =
- new Key<Rational[]>("android.sensor.colorTransform2", Rational[].class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_COLOR_TRANSFORM2 =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.colorTransform2", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <p>A matrix that transforms white balanced camera colors from the reference
@@ -1195,8 +1571,8 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
*/
- public static final Key<Rational[]> SENSOR_FORWARD_MATRIX1 =
- new Key<Rational[]>("android.sensor.forwardMatrix1", Rational[].class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_FORWARD_MATRIX1 =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.forwardMatrix1", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <p>A matrix that transforms white balanced camera colors from the reference
@@ -1217,21 +1593,8 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @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>
- *
- * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
- */
- public static final Key<Rational> SENSOR_BASE_GAIN_FACTOR =
- new Key<Rational>("android.sensor.baseGainFactor", Rational.class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_FORWARD_MATRIX2 =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.forwardMatrix2", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <p>A fixed black level offset for each of the color filter arrangement
@@ -1298,8 +1661,8 @@ public final class CameraCharacteristics extends CameraMetadata {
* android.statistics.faceIds and
* 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);
+ public static final Key<int[]> STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES =
+ new Key<int[]>("android.statistics.info.availableFaceDetectModes", int[].class);
/**
* <p>Maximum number of simultaneously detectable
@@ -1322,19 +1685,16 @@ public final class CameraCharacteristics extends CameraMetadata {
/**
* <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>
+ * tonemap curve that can be used for {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.</p>
* <p>If the actual number of points provided by the application (in
- * android.tonemap.curve*) is less than max, the camera device will
+ * {@link CaptureRequest#TONEMAP_CURVE 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
+ * @see CaptureRequest#TONEMAP_CURVE
*/
public static final Key<Integer> TONEMAP_MAX_CURVE_POINTS =
new Key<Integer>("android.tonemap.maxCurvePoints", int.class);
@@ -1347,8 +1707,8 @@ public final class CameraCharacteristics extends CameraMetadata {
*
* @see CaptureRequest#TONEMAP_MODE
*/
- public static final Key<byte[]> TONEMAP_AVAILABLE_TONE_MAP_MODES =
- new Key<byte[]>("android.tonemap.availableToneMapModes", byte[].class);
+ public static final Key<int[]> TONEMAP_AVAILABLE_TONE_MAP_MODES =
+ new Key<int[]>("android.tonemap.availableToneMapModes", int[].class);
/**
* <p>A list of camera LEDs that are available on this system.</p>
@@ -1364,15 +1724,16 @@ public final class CameraCharacteristics extends CameraMetadata {
* <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>
+ * <li>30fps at maximum resolution (== sensor resolution) is preferred, more than 20fps is required.</li>
+ * <li>Per frame control ({@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} <code>==</code> PER_FRAME_CONTROL)</li>
+ * <li>Manual sensor control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_SENSOR)</li>
+ * <li>Manual post-processing control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_POST_PROCESSING)</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 CameraCharacteristics#SYNC_MAX_LATENCY
* @see #INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
* @see #INFO_SUPPORTED_HARDWARE_LEVEL_FULL
*/
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 9d0e0e1..6f5099b 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -16,6 +16,8 @@
package android.hardware.camera2;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.graphics.ImageFormat;
import android.os.Handler;
import android.view.Surface;
@@ -147,7 +149,7 @@ public interface CameraDevice extends AutoCloseable {
* the size of the Surface with
* {@link android.view.SurfaceHolder#setFixedSize} to be one of the
* supported
- * {@link CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_SIZES processed sizes}
+ * {@link StreamConfigurationMap#getOutputSizes(Class) processed sizes}
* before calling {@link android.view.SurfaceHolder#getSurface}.</li>
*
* <li>For accessing through an OpenGL texture via a
@@ -155,14 +157,14 @@ public interface CameraDevice extends AutoCloseable {
* the SurfaceTexture with
* {@link android.graphics.SurfaceTexture#setDefaultBufferSize} to be one
* of the supported
- * {@link CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_SIZES processed sizes}
+ * {@link StreamConfigurationMap#getOutputSizes(Class) processed sizes}
* before creating a Surface from the SurfaceTexture with
* {@link Surface#Surface}.</li>
*
* <li>For recording with {@link android.media.MediaCodec}: Call
* {@link android.media.MediaCodec#createInputSurface} after configuring
* the media codec to use one of the
- * {@link CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_SIZES processed sizes}
+ * {@link StreamConfigurationMap#getOutputSizes(Class) processed sizes}
* </li>
*
* <li>For recording with {@link android.media.MediaRecorder}: TODO</li>
@@ -171,18 +173,15 @@ public interface CameraDevice extends AutoCloseable {
* Create a RenderScript
* {@link android.renderscript.Allocation Allocation} with a supported YUV
* type, the IO_INPUT flag, and one of the supported
- * {@link CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_SIZES processed sizes}. Then
+ * {@link StreamConfigurationMap#getOutputSizes(int) processed sizes}. Then
* obtain the Surface with
* {@link android.renderscript.Allocation#getSurface}.</li>
*
- * <li>For access to uncompressed or JPEG data in the application: Create a
- * {@link android.media.ImageReader} object with the desired
- * {@link CameraCharacteristics#SCALER_AVAILABLE_FORMATS image format}, and a
- * size from the matching
- * {@link CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_SIZES processed},
- * {@link CameraCharacteristics#SCALER_AVAILABLE_JPEG_SIZES jpeg}. Then obtain
- * a Surface from it.</li>
- *
+ * <li>For access to uncompressed or {@link ImageFormat#JPEG JPEG} data in the application:
+ * Create a {@link android.media.ImageReader} object with the desired
+ * {@link StreamConfigurationMap#getOutputFormats() image format}, and a size from the matching
+ * {@link StreamConfigurationMap#getOutputSizes(int) processed size} and {@code format}.
+ * Then obtain a {@link Surface} from it.</li>
* </ul>
*
* </p>
@@ -243,10 +242,126 @@ public interface CameraDevice extends AutoCloseable {
* @see StreamConfigurationMap#getOutputFormats()
* @see StreamConfigurationMap#getOutputSizes(int)
* @see StreamConfigurationMap#getOutputSizes(Class)
+ * @deprecated Use {@link #createCaptureSession} instead
*/
public void configureOutputs(List<Surface> outputs) throws CameraAccessException;
/**
+ * <p>Create a new camera capture session by providing the target output set of Surfaces to the
+ * camera device.</p>
+ *
+ * <p>The active capture session determines the set of potential output Surfaces for
+ * the camera device for each capture request. A given request may use all
+ * or a only some of the outputs. Once the CameraCaptureSession is created, requests can be
+ * can be submitted with {@link CameraCaptureSession#capture capture},
+ * {@link CameraCaptureSession#captureBurst captureBurst},
+ * {@link CameraCaptureSession#setRepeatingRequest setRepeatingRequest}, or
+ * {@link CameraCaptureSession#setRepeatingBurst setRepeatingBurst}.</p>
+ *
+ * <p>Surfaces suitable for inclusion as a camera output can be created for
+ * various use cases and targets:</p>
+ *
+ * <ul>
+ *
+ * <li>For drawing to a {@link android.view.SurfaceView SurfaceView}: Set the size of the
+ * Surface with {@link android.view.SurfaceHolder#setFixedSize} to be one of the sizes
+ * returned by
+ * {@link StreamConfigurationMap#getOutputSizes(Class) getOutputSizes(SurfaceView.class)}
+ * and then obtain the Surface by calling {@link android.view.SurfaceHolder#getSurface}.</li>
+ *
+ * <li>For accessing through an OpenGL texture via a
+ * {@link android.graphics.SurfaceTexture SurfaceTexture}: Set the size of
+ * the SurfaceTexture with
+ * {@link android.graphics.SurfaceTexture#setDefaultBufferSize} to be one
+ * of the sizes returned by
+ * {@link StreamConfigurationMap#getOutputSizes(Class) getOutputSizes(SurfaceTexture.class)}
+ * before creating a Surface from the SurfaceTexture with
+ * {@link Surface#Surface}.</li>
+ *
+ * <li>For recording with {@link android.media.MediaCodec}: Call
+ * {@link android.media.MediaCodec#createInputSurface} after configuring
+ * the media codec to use one of the sizes returned by
+ * {@link StreamConfigurationMap#getOutputSizes(Class) getOutputSizes(MediaCodec.class)}
+ * </li>
+ *
+ * <li>For recording with {@link android.media.MediaRecorder}: Call
+ * {@link android.media.MediaRecorder#getSurface} after configuring the media recorder to use
+ * one of the sizes returned by
+ * {@link StreamConfigurationMap#getOutputSizes(Class) getOutputSizes(MediaRecorder.class)},
+ * or configuring it to use one of the supported
+ * {@link android.media.CamcorderProfile CamcorderProfiles}.</li>
+ *
+ * <li>For efficient YUV processing with {@link android.renderscript}:
+ * Create a RenderScript
+ * {@link android.renderscript.Allocation Allocation} with a supported YUV
+ * type, the IO_INPUT flag, and one of the sizes returned by
+ * {@link StreamConfigurationMap#getOutputSizes(Class) getOutputSizes(Allocation.class)},
+ * Then obtain the Surface with
+ * {@link android.renderscript.Allocation#getSurface}.</li>
+ *
+ * <li>For access to raw, uncompressed or JPEG data in the application: Create a
+ * {@link android.media.ImageReader} object with the one of the supported
+ * {@link StreamConfigurationMap#getOutputFormats() output image formats}, and a
+ * size from the supported
+ * {@link StreamConfigurationMap#getOutputSizes(int) sizes for that format}. Then obtain
+ * a Surface from it with {@link android.media.ImageReader#getSurface}.</li>
+ *
+ * </ul>
+ *
+ * </p>
+ *
+ * <p>The camera device will query each Surface's size and formats upon this
+ * call, so they must be set to a valid setting at this time (in particular:
+ * if the format is user-visible, it must be one of
+ * {@link StreamConfigurationMap#getOutputFormats}; and the size must be one of
+ * {@link StreamConfigurationMap#getOutputSizes(int)}).</p>
+ *
+ * <p>It can take several hundred milliseconds for the session's configuration to complete,
+ * since camera hardware may need to be powered on or reconfigured. Once the configuration is
+ * complete and the session is ready to actually capture data, the provided
+ * {@link CameraCaptureSession.StateListener}'s
+ * {@link CameraCaptureSession.StateListener#onConfigured} callback will be called.</p>
+ *
+ * <p>If a prior CameraCaptureSession already exists when a new one is created, the previous
+ * session is closed. Any in-progress capture requests made on the prior session will be
+ * completed before the new session is configured and is able to start capturing its own
+ * requests. To minimize the transition time, the {@link CameraCaptureSession#abortCaptures}
+ * call can be used to discard the remaining requests for the prior capture session before a new
+ * one is created. Note that once the new session is created, the old one can no longer have its
+ * captures aborted.</p>
+ *
+ * <p>Using larger resolution outputs, or more outputs, can result in slower
+ * output rate from the device.</p>
+ *
+ * <p>Configuring a session with an empty or null list will close the current session, if
+ * any. This can be used to release the current session's target surfaces for another use.</p>
+ *
+ * @param outputs The new set of Surfaces that should be made available as
+ * targets for captured image data.
+ * @param listener The listener to notify about the status of the new capture session.
+ * @param handler The handler on which the listener should be invoked, or {@code null} to use
+ * the current thread's {@link android.os.Looper looper}.
+ * <!--
+ * @return A new camera capture session to use, or null if an empty/null set of Surfaces is
+ * provided.
+ * -->
+ * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
+ * the listener is null, or the handler is null but the current
+ * thread has no looper.
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if the camera device has been closed
+ *
+ * @see CameraCaptureSession
+ * @see StreamConfigurationMap#getOutputFormats()
+ * @see StreamConfigurationMap#getOutputSizes(int)
+ * @see StreamConfigurationMap#getOutputSizes(Class)
+ */
+ public void createCaptureSession(List<Surface> outputs,
+ CameraCaptureSession.StateListener listener, Handler handler)
+ throws CameraAccessException;
+
+ /**
* <p>Create a {@link CaptureRequest.Builder} for new capture requests,
* initialized with template for a target use case. The settings are chosen
* to be the best options for the specific camera device, so it is not
@@ -315,6 +430,7 @@ public interface CameraDevice extends AutoCloseable {
* @see #captureBurst
* @see #setRepeatingRequest
* @see #setRepeatingBurst
+ * @deprecated Use {@link CameraCaptureSession} instead
*/
public int capture(CaptureRequest request, CaptureListener listener, Handler handler)
throws CameraAccessException;
@@ -359,6 +475,7 @@ public interface CameraDevice extends AutoCloseable {
* @see #capture
* @see #setRepeatingRequest
* @see #setRepeatingBurst
+ * @deprecated Use {@link CameraCaptureSession} instead
*/
public int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
Handler handler) throws CameraAccessException;
@@ -417,6 +534,7 @@ public interface CameraDevice extends AutoCloseable {
* @see #setRepeatingBurst
* @see #stopRepeating
* @see #flush
+ * @deprecated Use {@link CameraCaptureSession} instead
*/
public int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
Handler handler) throws CameraAccessException;
@@ -475,6 +593,7 @@ public interface CameraDevice extends AutoCloseable {
* @see #setRepeatingRequest
* @see #stopRepeating
* @see #flush
+ * @deprecated Use {@link CameraCaptureSession} instead
*/
public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
Handler handler) throws CameraAccessException;
@@ -499,6 +618,7 @@ public interface CameraDevice extends AutoCloseable {
* @see #setRepeatingRequest
* @see #setRepeatingBurst
* @see StateListener#onIdle
+ * @deprecated Use {@link CameraCaptureSession} instead
*/
public void stopRepeating() throws CameraAccessException;
@@ -535,25 +655,24 @@ public interface CameraDevice extends AutoCloseable {
* @see #setRepeatingRequest
* @see #setRepeatingBurst
* @see #configureOutputs
+ * @deprecated Use {@link CameraCaptureSession} instead
*/
public void flush() throws CameraAccessException;
/**
- * Close the connection to this camera device.
+ * Close the connection to this camera device as quickly as possible.
*
- * <p>After this call, all calls to
- * the camera device interface will throw a {@link IllegalStateException},
- * except for calls to close(). Once the device has fully shut down, the
- * {@link StateListener#onClosed} callback will be called, and the camera is
- * free to be re-opened.</p>
+ * <p>Immediately after this call, all calls to the camera device or active session interface
+ * will throw a {@link IllegalStateException}, except for calls to close(). Once the device has
+ * fully shut down, the {@link StateListener#onClosed} callback will be called, and the camera
+ * is free to be re-opened.</p>
*
- * <p>After this call, besides the final {@link StateListener#onClosed} call, no calls to the
- * device's {@link StateListener} will occur, and any remaining submitted capture requests will
- * not fire their {@link CaptureListener} callbacks.</p>
+ * <p>Immediately after this call, besides the final {@link StateListener#onClosed} calls, no
+ * further callbacks from the device or the active session will occur, and any remaining
+ * submitted capture requests will be discarded, as if
+ * {@link CameraCaptureSession#abortCaptures} had been called, except that no success or failure
+ * callbacks will be invoked.</p>
*
- * <p>To shut down as fast as possible, call the {@link #flush} method and then {@link #close}
- * once the flush completes. This will discard some capture requests, but results in faster
- * shutdown.</p>
*/
@Override
public void close();
@@ -570,6 +689,7 @@ public interface CameraDevice extends AutoCloseable {
* @see #captureBurst
* @see #setRepeatingRequest
* @see #setRepeatingBurst
+ * @deprecated Use {@link CameraCaptureSession} instead
*/
public static abstract class CaptureListener {
@@ -646,14 +766,62 @@ public interface CameraDevice extends AutoCloseable {
}
/**
- * This method is called when an image capture has completed and the
+ * This method is called when an image capture makes partial forward progress; some
+ * (but not all) results from an image capture are available.
+ *
+ * <p>The result provided here will contain some subset of the fields of
+ * a full result. Multiple {@link #onCaptureProgressed} calls may happen per
+ * capture; a given result field will only be present in one partial
+ * capture at most. The final {@link #onCaptureCompleted} call will always
+ * contain all the fields (in particular, the union of all the fields of all
+ * the partial results composing the total result).</p>
+ *
+ * <p>For each request, some result data might be available earlier than others. The typical
+ * delay between each partial result (per request) is a single frame interval.
+ * For performance-oriented use-cases, applications should query the metadata they need
+ * to make forward progress from the partial results and avoid waiting for the completed
+ * result.</p>
+ *
+ * <p>Each request will generate at least {@code 1} partial results, and at most
+ * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT} partial results.</p>
+ *
+ * <p>Depending on the request settings, the number of partial results per request
+ * will vary, although typically the partial count could be the same as long as the
+ * camera device subsystems enabled stay the same.</p>
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param camera The CameraDevice sending the callback.
+ * @param request The request that was given to the CameraDevice
+ * @param partialResult The partial output metadata from the capture, which
+ * includes a subset of the {@link TotalCaptureResult} fields.
+ *
+ * @see #capture
+ * @see #captureBurst
+ * @see #setRepeatingRequest
+ * @see #setRepeatingBurst
+ */
+ public void onCaptureProgressed(CameraDevice camera,
+ CaptureRequest request, CaptureResult partialResult) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called when an image capture has fully completed and all the
* result metadata is available.
*
+ * <p>This callback will always fire after the last {@link #onCaptureProgressed};
+ * in other words, no more partial results will be delivered once the completed result
+ * is available.</p>
+ *
+ * <p>For performance-intensive use-cases where latency is a factor, consider
+ * using {@link #onCaptureProgressed} instead.</p>
+ *
* <p>The default implementation of this method does nothing.</p>
*
* @param camera The CameraDevice sending the callback.
* @param request The request that was given to the CameraDevice
- * @param result The output metadata from the capture, including the
+ * @param result The total output metadata from the capture, including the
* final capture parameters and the state of the camera system during
* capture.
*
@@ -663,7 +831,7 @@ public interface CameraDevice extends AutoCloseable {
* @see #setRepeatingBurst
*/
public void onCaptureCompleted(CameraDevice camera,
- CaptureRequest request, CaptureResult result) {
+ CaptureRequest request, TotalCaptureResult result) {
// default empty implementation
}
@@ -676,6 +844,10 @@ public interface CameraDevice extends AutoCloseable {
* the capture may have been pushed to their respective output
* streams.</p>
*
+ * <p>Some partial results may have been delivered before the capture fails;
+ * however after this callback fires, no more partial results will be delivered by
+ * {@link #onCaptureProgressed}.</p>
+ *
* <p>The default implementation of this method does nothing.</p>
*
* @param camera
@@ -701,24 +873,57 @@ public interface CameraDevice extends AutoCloseable {
* when a capture sequence finishes and all {@link CaptureResult}
* or {@link CaptureFailure} for it have been returned via this listener.
*
+ * <p>In total, there will be at least one result/failure returned by this listener
+ * before this callback is invoked. If the capture sequence is aborted before any
+ * requests have been processed, {@link #onCaptureSequenceAborted} is invoked instead.</p>
+ *
+ * <p>The default implementation does nothing.</p>
+ *
* @param camera
* The CameraDevice sending the callback.
* @param sequenceId
* A sequence ID returned by the {@link #capture} family of functions.
- * @param lastFrameNumber
+ * @param frameNumber
* 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()
* @see CaptureResult#getSequenceId()
* @see CaptureFailure#getSequenceId()
+ * @see #onCaptureSequenceAborted
*/
public void onCaptureSequenceCompleted(CameraDevice camera,
- int sequenceId, int lastFrameNumber) {
+ int sequenceId, long frameNumber) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called independently of the others in CaptureListener,
+ * when a capture sequence aborts before any {@link CaptureResult}
+ * or {@link CaptureFailure} for it have been returned via this listener.
+ *
+ * <p>Due to the asynchronous nature of the camera device, not all submitted captures
+ * are immediately processed. It is possible to clear out the pending requests
+ * by a variety of operations such as {@link CameraDevice#stopRepeating} or
+ * {@link CameraDevice#flush}. When such an event happens,
+ * {@link #onCaptureSequenceCompleted} will not be called.</p>
+ *
+ * <p>The default implementation does nothing.</p>
+ *
+ * @param camera
+ * The CameraDevice sending the callback.
+ * @param sequenceId
+ * A sequence ID returned by the {@link #capture} family of functions.
+ *
+ * @see CaptureResult#getFrameNumber()
+ * @see CaptureFailure#getFrameNumber()
+ * @see CaptureResult#getSequenceId()
+ * @see CaptureFailure#getSequenceId()
+ * @see #onCaptureSequenceCompleted
+ */
+ public void onCaptureSequenceAborted(CameraDevice camera,
+ int sequenceId) {
// default empty implementation
}
}
@@ -835,6 +1040,7 @@ public interface CameraDevice extends AutoCloseable {
* <p>The default implementation of this method does nothing.</p>
*
* @param camera the camera device has that become unconfigured
+ * @deprecated Use {@link CameraCaptureSession.StateListener} instead.
*/
public void onUnconfigured(CameraDevice camera) {
// Default empty implementation
@@ -864,6 +1070,7 @@ public interface CameraDevice extends AutoCloseable {
* @see CameraDevice#captureBurst
* @see CameraDevice#setRepeatingBurst
* @see CameraDevice#setRepeatingRequest
+ * @deprecated Use {@link CameraCaptureSession.StateListener} instead.
*/
public void onActive(CameraDevice camera) {
// Default empty implementation
@@ -897,6 +1104,7 @@ public interface CameraDevice extends AutoCloseable {
*
* @see CameraDevice#configureOutputs
* @see CameraDevice#flush
+ * @deprecated Use {@link CameraCaptureSession.StateListener} instead.
*/
public void onBusy(CameraDevice camera) {
// Default empty implementation
@@ -944,6 +1152,7 @@ public interface CameraDevice extends AutoCloseable {
* @see CameraDevice#configureOutputs
* @see CameraDevice#stopRepeating
* @see CameraDevice#flush
+ * @deprecated Use {@link CameraCaptureSession.StateListener} instead.
*/
public void onIdle(CameraDevice camera) {
// Default empty implementation
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 0fcd598..4a89fe7 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceListener;
import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.legacy.CameraDeviceUserShim;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
import android.hardware.camera2.utils.BinderHolder;
@@ -83,9 +84,8 @@ public final class CameraManager {
try {
CameraBinderDecorator.throwOnError(
CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor());
- } catch(CameraRuntimeException e) {
- throw new IllegalStateException("Failed to setup camera vendor tags",
- e.asChecked());
+ } catch (CameraRuntimeException e) {
+ handleRecoverableSetupErrors(e, "Failed to set up vendor tags");
}
try {
@@ -194,16 +194,11 @@ public final class CameraManager {
// impossible
return null;
}
-
return new CameraCharacteristics(info);
}
/**
- * Open a connection to a camera with the given ID. Use
- * {@link #getCameraIdList} to get the list of available camera
- * devices. Note that even if an id is listed, open may fail if the device
- * is disconnected between the calls to {@link #getCameraIdList} and
- * {@link #openCamera}.
+ * Helper for openning a connection to a camera with the given ID.
*
* @param cameraId The unique identifier of the camera device to open
* @param listener The listener for the camera. Must not be null.
@@ -216,35 +211,53 @@ public final class CameraManager {
* @throws SecurityException if the application does not have permission to
* access the camera
* @throws IllegalArgumentException if listener or handler is null.
+ * @return A handle to the newly-created camera device.
*
* @see #getCameraIdList
* @see android.app.admin.DevicePolicyManager#setCameraDisabled
*/
- private void openCameraDeviceUserAsync(String cameraId,
+ private CameraDevice openCameraDeviceUserAsync(String cameraId,
CameraDevice.StateListener listener, Handler handler)
throws CameraAccessException {
+ CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
+ CameraDevice device = null;
try {
synchronized (mLock) {
ICameraDeviceUser cameraUser;
- android.hardware.camera2.impl.CameraDevice device =
+ android.hardware.camera2.impl.CameraDevice deviceImpl =
new android.hardware.camera2.impl.CameraDevice(
cameraId,
listener,
- handler);
+ handler,
+ characteristics);
BinderHolder holder = new BinderHolder();
- mCameraService.connectDevice(device.getCallbacks(),
- Integer.parseInt(cameraId),
- mContext.getPackageName(), USE_CALLING_UID, holder);
- cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
+
+ ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
+ int id = Integer.parseInt(cameraId);
+ try {
+ mCameraService.connectDevice(callbacks, id, mContext.getPackageName(),
+ USE_CALLING_UID, holder);
+ cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
+ } catch (CameraRuntimeException e) {
+ if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) {
+ // Use legacy camera implementation for HAL1 devices
+ Log.i(TAG, "Using legacy camera HAL.");
+ cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id);
+ } else {
+ // Rethrow otherwise
+ throw e;
+ }
+ }
// TODO: factor out listener to be non-nested, then move setter to constructor
// For now, calling setRemoteDevice will fire initial
// onOpened/onUnconfigured callbacks.
- device.setRemoteDevice(cameraUser);
+ deviceImpl.setRemoteDevice(cameraUser);
+ device = deviceImpl;
}
} catch (NumberFormatException e) {
@@ -255,6 +268,7 @@ public final class CameraManager {
} catch (RemoteException e) {
// impossible
}
+ return device;
}
/**
@@ -265,20 +279,26 @@ public final class CameraManager {
* is disconnected between the calls to {@link #getCameraIdList} and
* {@link #openCamera}.</p>
*
- * <p>If the camera successfully opens after this function call returns,
- * {@link CameraDevice.StateListener#onOpened} will be invoked with the
- * newly opened {@link CameraDevice} in the unconfigured state.</p>
+ * <p>Once the camera is successfully opened, {@link CameraDevice.StateListener#onOpened} will
+ * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up
+ * for operation by calling {@link CameraDevice#createCaptureSession} and
+ * {@link CameraDevice#createCaptureRequest}</p>
*
+ * <!--
+ * <p>Since the camera device will be opened asynchronously, any asynchronous operations done
+ * on the returned CameraDevice instance will be queued up until the device startup has
+ * completed and the listener's {@link CameraDevice.StateListener#onOpened onOpened} method is
+ * called. The pending operations are then processed in order.</p>
+ * -->
* <p>If the camera becomes disconnected during initialization
* after this function call returns,
* {@link CameraDevice.StateListener#onDisconnected} with a
* {@link CameraDevice} in the disconnected state (and
* {@link CameraDevice.StateListener#onOpened} will be skipped).</p>
*
- * <p>If the camera fails to initialize after this function call returns,
- * {@link CameraDevice.StateListener#onError} will be invoked with a
- * {@link CameraDevice} in the error state (and
- * {@link CameraDevice.StateListener#onOpened} will be skipped).</p>
+ * <p>If opening the camera device fails, then the device listener's
+ * {@link CameraDevice.StateListener#onError onError} method will be called, and subsequent
+ * calls on the camera device will throw an {@link IllegalStateException}.</p>
*
* @param cameraId
* The unique identifier of the camera device to open
@@ -405,6 +425,18 @@ public final class CameraManager {
return mDeviceIdList;
}
+ private void handleRecoverableSetupErrors(CameraRuntimeException e, String msg) {
+ int problem = e.getReason();
+ switch (problem) {
+ case CameraAccessException.CAMERA_DISCONNECTED:
+ String errorMsg = CameraAccessException.getDefaultMessage(problem);
+ Log.w(TAG, msg + ": " + errorMsg);
+ break;
+ default:
+ throw new IllegalStateException(msg, e.asChecked());
+ }
+ }
+
// TODO: this class needs unit tests
// TODO: extract class into top level
private class CameraServiceListener extends ICameraServiceListener.Stub {
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 6e38a22..b3e165e 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -16,7 +16,7 @@
package android.hardware.camera2;
-import android.hardware.camera2.impl.CameraMetadataNative;
+import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
@@ -35,7 +35,7 @@ import java.util.List;
*
* <p>
* All instances of CameraMetadata are immutable. The list of keys with {@link #getKeys()}
- * never changes, nor do the values returned by any key with {@link #get} throughout
+ * never changes, nor do the values returned by any key with {@code #get} throughout
* the lifetime of the object.
* </p>
*
@@ -43,7 +43,10 @@ import java.util.List;
* @see CameraManager
* @see CameraCharacteristics
**/
-public abstract class CameraMetadata {
+public abstract class CameraMetadata<TKey> {
+
+ private static final String TAG = "CameraMetadataAb";
+ private static final boolean VERBOSE = false;
/**
* Set a camera metadata field to a value. The field definitions can be
@@ -73,8 +76,15 @@ public abstract class CameraMetadata {
*
* @param key The metadata field to read.
* @return The value of that key, or {@code null} if the field is not set.
+ *
+ * @hide
*/
- public abstract <T> T get(Key<T> key);
+ protected abstract <T> T getProtected(TKey key);
+
+ /**
+ * @hide
+ */
+ protected abstract Class<TKey> getKeyClass();
/**
* Returns a list of the keys contained in this map.
@@ -82,14 +92,16 @@ public abstract class CameraMetadata {
* <p>The list returned is not modifiable, so any attempts to modify it will throw
* a {@code UnsupportedOperationException}.</p>
*
- * <p>All values retrieved by a key from this list with {@link #get} are guaranteed to be
+ * <p>All values retrieved by a key from this list with {@code #get} are guaranteed to be
* non-{@code null}. Each key is only listed once in the list. The order of the keys
* is undefined.</p>
*
* @return List of the keys contained in this map.
*/
- public List<Key<?>> getKeys() {
- return Collections.unmodifiableList(getKeysStatic(this.getClass(), this));
+ @SuppressWarnings("unchecked")
+ public List<TKey> getKeys() {
+ Class<CameraMetadata<TKey>> thisClass = (Class<CameraMetadata<TKey>>) getClass();
+ return Collections.unmodifiableList(getKeysStatic(thisClass, getKeyClass(), this));
}
/**
@@ -100,24 +112,31 @@ public abstract class CameraMetadata {
* Optionally, if {@code instance} is not null, then filter out any keys with null values.
* </p>
*/
- /*package*/ static ArrayList<Key<?>> getKeysStatic(Class<? extends CameraMetadata> type,
- CameraMetadata instance) {
- ArrayList<Key<?>> keyList = new ArrayList<Key<?>>();
+ /*package*/ @SuppressWarnings("unchecked")
+ static <TKey> ArrayList<TKey> getKeysStatic(
+ Class<?> type, Class<TKey> keyClass,
+ CameraMetadata<TKey> instance) {
+
+ if (VERBOSE) Log.v(TAG, "getKeysStatic for " + type);
+
+ ArrayList<TKey> keyList = new ArrayList<TKey>();
Field[] fields = type.getDeclaredFields();
for (Field field : fields) {
// Filter for Keys that are public
- if (field.getType().isAssignableFrom(Key.class) &&
+ if (field.getType().isAssignableFrom(keyClass) &&
(field.getModifiers() & Modifier.PUBLIC) != 0) {
- Key<?> key;
+
+ TKey key;
try {
- key = (Key<?>) field.get(instance);
+ key = (TKey) field.get(instance);
} catch (IllegalAccessException e) {
throw new AssertionError("Can't get IllegalAccessException", e);
} catch (IllegalArgumentException e) {
throw new AssertionError("Can't get IllegalArgumentException", e);
}
- if (instance == null || instance.get(key) != null) {
+
+ if (instance == null || instance.getProtected(key) != null) {
keyList.add(key);
}
}
@@ -126,79 +145,6 @@ public abstract class CameraMetadata {
return keyList;
}
- public static class Key<T> {
-
- private boolean mHasTag;
- private int mTag;
- private final Class<T> mType;
- private final String mName;
-
- /**
- * @hide
- */
- public Key(String name, Class<T> type) {
- if (name == null) {
- throw new NullPointerException("Key needs a valid name");
- } else if (type == null) {
- throw new NullPointerException("Type needs to be non-null");
- }
- mName = name;
- mType = type;
- }
-
- public final String getName() {
- return mName;
- }
-
- @Override
- public final int hashCode() {
- return mName.hashCode();
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public final boolean equals(Object o) {
- if (this == o) {
- return true;
- }
-
- if (!(o instanceof Key)) {
- return false;
- }
-
- Key lhs = (Key) o;
-
- return mName.equals(lhs.mName) && mType.equals(lhs.mType);
- }
-
- /**
- * <p>
- * Get the tag corresponding to this key. This enables insertion into the
- * native metadata.
- * </p>
- *
- * <p>This value is looked up the first time, and cached subsequently.</p>
- *
- * @return The tag numeric value corresponding to the string
- *
- * @hide
- */
- public final int getTag() {
- if (!mHasTag) {
- mTag = CameraMetadataNative.getTag(mName);
- mHasTag = true;
- }
- return mTag;
- }
-
- /**
- * @hide
- */
- public final Class<T> getType() {
- return mType;
- }
- }
-
/*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* The enum values below this point are generated from metadata
* definitions in /system/media/camera/docs. Do not modify by hand or
@@ -290,9 +236,17 @@ public abstract class CameraMetadata {
/**
* <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>
+ * as auto exposure, and auto focus can be bypassed).
+ * The camera device supports basic manual control of the sensor image
+ * acquisition related stages. This means the following controls are
+ * guaranteed to be supported:</p>
* <ul>
+ * <li>Manual frame duration control<ul>
+ * <li>{@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li>
+ * <li>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</li>
+ * </ul>
+ * </li>
* <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>
@@ -301,7 +255,6 @@ public abstract class CameraMetadata {
* <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>
@@ -320,11 +273,15 @@ public abstract class CameraMetadata {
* <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>
+ * <p>A given camera device may also support additional manual sensor controls,
+ * but this capability only covers the above list of controls.</p>
*
* @see CaptureRequest#BLACK_LEVEL_LOCK
- * @see CameraCharacteristics#SENSOR_BASE_GAIN_FACTOR
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
* @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
* @see CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE
+ * @see CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION
* @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE
* @see CaptureRequest#SENSOR_SENSITIVITY
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
@@ -332,12 +289,12 @@ public abstract class CameraMetadata {
public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 2;
/**
- * <p>TODO: This should be @hide</p>
+ * <p>The camera device post-processing stages can be manually controlled.
+ * The camera device supports basic manual control of the image post-processing
+ * stages. This means the following controls are guaranteed to be supported:</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_CURVE android.tonemap.curve}</li>
* <li>{@link CaptureRequest#TONEMAP_MODE android.tonemap.mode}</li>
* <li>{@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</li>
* </ul>
@@ -348,8 +305,8 @@ public abstract class CameraMetadata {
* </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>
+ * <li>android.statistics.lensShadingMap</li>
+ * <li>android.lens.info.shadingMapSize</li>
* </ul>
* </li>
* </ul>
@@ -357,19 +314,17 @@ public abstract class CameraMetadata {
* 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>
+ * <p>A given camera device may also support additional post-processing
+ * controls, but this capability only covers the above list of controls.</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 CaptureRequest#TONEMAP_CURVE
* @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
* @see CaptureRequest#TONEMAP_MODE
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
*/
- public static final int REQUEST_AVAILABLE_CAPABILITIES_GCAM = 3;
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 3;
/**
* <p>The camera device supports the Zero Shutter Lag use case.</p>
@@ -411,18 +366,20 @@ public abstract class CameraMetadata {
public static final int REQUEST_AVAILABLE_CAPABILITIES_DNG = 5;
//
- // Enumeration values for CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS
+ // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE
//
/**
- * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS
+ * <p>The camera device will only support centered crop regions.</p>
+ * @see CameraCharacteristics#SCALER_CROPPING_TYPE
*/
- public static final int SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT = 0;
+ public static final int SCALER_CROPPING_TYPE_CENTER_ONLY = 0;
/**
- * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS
+ * <p>The camera device will support arbitrarily chosen crop regions.</p>
+ * @see CameraCharacteristics#SCALER_CROPPING_TYPE
*/
- public static final int SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT = 1;
+ public static final int SCALER_CROPPING_TYPE_FREEFORM = 1;
//
// Enumeration values for CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT
@@ -1344,8 +1301,7 @@ public abstract class CameraMetadata {
/**
* <p>If the flash is available and charged, fire flash
- * for this capture based on android.flash.firingPower and
- * android.flash.firingTime.</p>
+ * for this capture.</p>
* @see CaptureRequest#FLASH_MODE
*/
public static final int FLASH_MODE_SINGLE = 1;
@@ -1603,17 +1559,14 @@ public abstract class CameraMetadata {
/**
* <p>Use the tone mapping curve specified in
- * the android.tonemap.curve* entries.</p>
+ * the {@link CaptureRequest#TONEMAP_CURVE 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>
+ * {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.</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_CURVE
* @see CaptureRequest#TONEMAP_MODE
*/
public static final int TONEMAP_MODE_CONTRAST_CURVE = 0;
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index f161f3a..d4dfdd5 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -16,12 +16,18 @@
package android.hardware.camera2;
+import android.hardware.camera2.CameraCharacteristics.Key;
import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.utils.TypeReference;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Rational;
import android.view.Surface;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Objects;
@@ -55,7 +61,98 @@ import java.util.Objects;
* @see CameraDevice#setRepeatingRequest
* @see CameraDevice#createCaptureRequest
*/
-public final class CaptureRequest extends CameraMetadata implements Parcelable {
+public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
+ implements Parcelable {
+
+ /**
+ * A {@code Key} is used to do capture request field lookups with
+ * {@link CaptureResult#get} or to set fields with
+ * {@link CaptureRequest.Builder#set(Key, Object)}.
+ *
+ * <p>For example, to set the crop rectangle for the next capture:
+ * <code><pre>
+ * Rect cropRectangle = new Rect(0, 0, 640, 480);
+ * captureRequestBuilder.set(SCALER_CROP_REGION, cropRectangle);
+ * </pre></code>
+ * </p>
+ *
+ * <p>To enumerate over all possible keys for {@link CaptureResult}, see
+ * {@link CameraCharacteristics#getAvailableCaptureResultKeys}.</p>
+ *
+ * @see CaptureResult#get
+ * @see CameraCharacteristics#getAvailableCaptureResultKeys
+ */
+ public final static class Key<T> {
+ private final CameraMetadataNative.Key<T> mKey;
+
+ /**
+ * Visible for testing and vendor extensions only.
+ *
+ * @hide
+ */
+ public Key(String name, Class<T> type) {
+ mKey = new CameraMetadataNative.Key<T>(name, type);
+ }
+
+ /**
+ * Visible for testing and vendor extensions only.
+ *
+ * @hide
+ */
+ public Key(String name, TypeReference<T> typeReference) {
+ mKey = new CameraMetadataNative.Key<T>(name, typeReference);
+ }
+
+ /**
+ * Return a camelCase, period separated name formatted like:
+ * {@code "root.section[.subsections].name"}.
+ *
+ * <p>Built-in keys exposed by the Android SDK are always prefixed with {@code "android."};
+ * keys that are device/platform-specific are prefixed with {@code "com."}.</p>
+ *
+ * <p>For example, {@code CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP} would
+ * have a name of {@code "android.scaler.streamConfigurationMap"}; whereas a device
+ * specific key might look like {@code "com.google.nexus.data.private"}.</p>
+ *
+ * @return String representation of the key name
+ */
+ public String getName() {
+ return mKey.getName();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final int hashCode() {
+ return mKey.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public final boolean equals(Object o) {
+ return o instanceof Key && ((Key<T>)o).mKey.equals(mKey);
+ }
+
+ /**
+ * Visible for CameraMetadataNative implementation only; do not use.
+ *
+ * TODO: Make this private or remove it altogether.
+ *
+ * @hide
+ */
+ public CameraMetadataNative.Key<T> getNativeKey() {
+ return mKey;
+ }
+
+ @SuppressWarnings({ "unchecked" })
+ /*package*/ Key(CameraMetadataNative.Key<?> nativeKey) {
+ mKey = (CameraMetadataNative.Key<T>) nativeKey;
+ }
+ }
private final HashSet<Surface> mSurfaceSet;
private final CameraMetadataNative mSettings;
@@ -90,17 +187,58 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* Used by the Builder to create a mutable CaptureRequest.
*/
private CaptureRequest(CameraMetadataNative settings) {
- mSettings = settings;
+ mSettings = CameraMetadataNative.move(settings);
mSurfaceSet = new HashSet<Surface>();
}
- @SuppressWarnings("unchecked")
- @Override
+ /**
+ * Get a capture request field value.
+ *
+ * <p>The field definitions can be found in {@link CaptureRequest}.</p>
+ *
+ * <p>Querying the value for the same key more than once will return a value
+ * which is equal to the previous queried value.</p>
+ *
+ * @throws IllegalArgumentException if the key was not valid
+ *
+ * @param key The result field to read.
+ * @return The value of that key, or {@code null} if the field is not set.
+ */
public <T> T get(Key<T> key) {
return mSettings.get(key);
}
/**
+ * {@inheritDoc}
+ * @hide
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ protected <T> T getProtected(Key<?> key) {
+ return (T) mSettings.get(key);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ protected Class<Key<?>> getKeyClass() {
+ Object thisClass = Key.class;
+ return (Class<Key<?>>)thisClass;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Key<?>> getKeys() {
+ // Force the javadoc for this function to show up on the CaptureRequest page
+ return super.getKeys();
+ }
+
+ /**
* Retrieve the tag for this request, if any.
*
* <p>This tag is not used for anything by the camera device, but can be
@@ -169,7 +307,7 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* @param in The parcel from which the object should be read
* @hide
*/
- public void readFromParcel(Parcel in) {
+ private void readFromParcel(Parcel in) {
mSettings.readFromParcel(in);
mSurfaceSet.clear();
@@ -198,6 +336,20 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
}
/**
+ * @hide
+ */
+ public boolean containsTarget(Surface surface) {
+ return mSurfaceSet.contains(surface);
+ }
+
+ /**
+ * @hide
+ */
+ public Collection<Surface> getTargets() {
+ return Collections.unmodifiableCollection(mSurfaceSet);
+ }
+
+ /**
* A builder for capture requests.
*
* <p>To obtain a builder instance, use the
@@ -385,30 +537,24 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
*
* @see CaptureRequest#COLOR_CORRECTION_MODE
*/
- public static final Key<Rational[]> COLOR_CORRECTION_TRANSFORM =
- new Key<Rational[]>("android.colorCorrection.transform", Rational[].class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> COLOR_CORRECTION_TRANSFORM =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.colorCorrection.transform", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <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 <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
- * {@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>
+ * <p>These per-channel gains are 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 {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is
+ * TRANSFORM_MATRIX.</p>
+ * <p>The gains in the result metadata are 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);
+ public static final Key<android.hardware.camera2.params.RggbChannelVector> COLOR_CORRECTION_GAINS =
+ new Key<android.hardware.camera2.params.RggbChannelVector>("android.colorCorrection.gains", android.hardware.camera2.params.RggbChannelVector.class);
/**
* <p>The desired setting for the camera device's auto-exposure
@@ -458,7 +604,19 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* 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 EV. Note that this control will only be effective
+ * if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} <code>!=</code> OFF. This control will take effect even when
+ * {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} <code>== true</code>.</p>
+ * <p>In the event of exposure compensation value being changed, camera device
+ * may take several frames to reach the newly requested exposure target.
+ * During that time, {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} field will be in the SEARCHING
+ * state. Once the new exposure target is reached, {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} will
+ * change from SEARCHING to either CONVERGED, LOCKED (if AE lock is enabled), or
+ * FLASH_REQUIRED (if the scene is too dark for still capture).</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_LOCK
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureResult#CONTROL_AE_STATE
*/
public static final Key<Integer> CONTROL_AE_EXPOSURE_COMPENSATION =
new Key<Integer>("android.control.aeExposureCompensation", int.class);
@@ -469,6 +627,8 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* <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>When {@link CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION android.control.aeExposureCompensation} is changed, even if the AE lock
+ * is ON, the camera device will still adjust its exposure value.</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})
@@ -477,6 +637,7 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* {@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_EXPOSURE_COMPENSATION
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
* @see CaptureResult#CONTROL_AE_STATE
@@ -526,26 +687,27 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
/**
* <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
* ({@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>
+ * bottom-right pixel in the active pixel array.</p>
+ * <p>The weight must range from 0 to 1000, and represents a weight
+ * for every pixel in the area. This means that a large metering area
+ * with the same weight as a smaller area will have more effect in
+ * the metering result. Metering areas can partially overlap and the
+ * camera device will add the weights in the overlap region.</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>
+ * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata,
+ * the camera device will ignore the sections outside the region and output the
+ * used sections in the result 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);
+ public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AE_REGIONS =
+ new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.aeRegions", android.hardware.camera2.params.MeteringRectangle[].class);
/**
* <p>Range over which fps can be adjusted to
@@ -555,8 +717,8 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
*
* @see CaptureRequest#SENSOR_EXPOSURE_TIME
*/
- public static final Key<int[]> CONTROL_AE_TARGET_FPS_RANGE =
- new Key<int[]>("android.control.aeTargetFpsRange", int[].class);
+ public static final Key<android.util.Range<Integer>> CONTROL_AE_TARGET_FPS_RANGE =
+ new Key<android.util.Range<Integer>>("android.control.aeTargetFpsRange", new TypeReference<android.util.Range<Integer>>() {{ }});
/**
* <p>Whether the camera device will trigger a precapture
@@ -601,26 +763,27 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
/**
* <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
* ({@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 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>
+ * bottom-right pixel in the active pixel array.</p>
+ * <p>The weight must range from 0 to 1000, and represents a weight
+ * for every pixel in the area. This means that a large metering area
+ * with the same weight as a smaller area will have more effect in
+ * the metering result. Metering areas can partially overlap and the
+ * camera device will add the weights in the overlap region.</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 used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata,
+ * the camera device will ignore the sections outside the region and output the
+ * used sections in the result 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);
+ public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AF_REGIONS =
+ new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.afRegions", android.hardware.camera2.params.MeteringRectangle[].class);
/**
* <p>Whether the camera device will trigger autofocus for this request.</p>
@@ -687,27 +850,27 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
/**
* <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
* ({@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 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>
+ * bottom-right pixel in the active pixel array.</p>
+ * <p>The weight must range from 0 to 1000, and represents a weight
+ * for every pixel in the area. This means that a large metering area
+ * with the same weight as a smaller area will have more effect in
+ * the metering result. Metering areas can partially overlap and the
+ * camera device will add the weights in the overlap region.</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 used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata,
+ * the camera device will ignore the sections outside the region and output the
+ * used sections in the result 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);
+ public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS =
+ new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.awbRegions", android.hardware.camera2.params.MeteringRectangle[].class);
/**
* <p>Information to the camera device 3A (auto-exposure,
@@ -901,8 +1064,15 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.hotPixel.mode", int.class);
/**
+ * <p>A location object to use when generating image GPS metadata.</p>
+ */
+ public static final Key<android.location.Location> JPEG_GPS_LOCATION =
+ new Key<android.location.Location>("android.jpeg.gpsLocation", android.location.Location.class);
+
+ /**
* <p>GPS coordinates to include in output JPEG
* EXIF</p>
+ * @hide
*/
public static final Key<double[]> JPEG_GPS_COORDINATES =
new Key<double[]>("android.jpeg.gpsCoordinates", double[].class);
@@ -910,6 +1080,7 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
/**
* <p>32 characters describing GPS algorithm to
* include in EXIF</p>
+ * @hide
*/
public static final Key<String> JPEG_GPS_PROCESSING_METHOD =
new Key<String>("android.jpeg.gpsProcessingMethod", String.class);
@@ -917,6 +1088,7 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
/**
* <p>Time GPS fix was made to include in
* EXIF</p>
+ * @hide
*/
public static final Key<Long> JPEG_GPS_TIMESTAMP =
new Key<Long>("android.jpeg.gpsTimestamp", long.class);
@@ -949,9 +1121,15 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* 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>
+ * <p>If the thumbnail image aspect ratio differs from the JPEG primary image aspect
+ * ratio, the camera device creates the thumbnail by cropping it from the primary image.
+ * For example, if the primary image has 4:3 aspect ratio, the thumbnail image has
+ * 16:9 aspect ratio, the primary image will be cropped vertically (letterbox) to
+ * generate the thumbnail image. The thumbnail image will always have a smaller Field
+ * Of View (FOV) than the primary image when aspect ratios differ.</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);
+ public static final Key<android.util.Size> JPEG_THUMBNAIL_SIZE =
+ new Key<android.util.Size>("android.jpeg.thumbnailSize", android.util.Size.class);
/**
* <p>The ratio of lens focal length to the effective
@@ -1094,8 +1272,11 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* 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>The crop region is applied after the RAW to other color space (e.g. YUV)
+ * conversion. Since raw streams (e.g. RAW16) don't have the conversion stage,
+ * it is not croppable. The crop region will be ignored by raw streams.</p>
+ * <p>For non-raw streams, any additional per-stream cropping will
+ * 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
@@ -1170,7 +1351,8 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* 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.
+ * is provided via the {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} field
+ * using StreamConfigurationMap#getOutputMinFrameDuration(int, Size).
* 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
@@ -1180,7 +1362,8 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* <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
+ * looking it up in {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} using
+ * StreamConfigurationMap#getOutputMinFrameDuration(int, Size) (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
@@ -1188,7 +1371,8 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* 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
+ * StreamConfigurationMap#getOutputStallDuration(int,Size) using its
+ * respective size/format), 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>
@@ -1199,10 +1383,9 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* 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>
+ * StreamConfigurationMap#getOutputStallDuration(int,Size).</p>
*
- * @see CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS
- * @see CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
*/
public static final Key<Long> SENSOR_FRAME_DURATION =
new Key<Long>("android.sensor.frameDuration", long.class);
@@ -1260,8 +1443,8 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* <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
+ * shading map with size specified as <code>android.lens.info.shadingMapSize = [ 4, 3 ]</code>,
+ * the output 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,
@@ -1273,11 +1456,17 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* <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>
+ * lens shading map data in android.statistics.lensShadingMap, with size specified
+ * by android.lens.info.shadingMapSize; the returned shading map data will be the one
+ * applied by the camera device for this capture request.</p>
+ * <p>The shading map data may depend on the AE and AWB statistics, therefore the reliability
+ * of the map data may be affected by the AE and AWB algorithms. When AE and AWB are in
+ * AUTO modes({@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} <code>!=</code> OFF and {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} <code>!=</code> OFF),
+ * to get best results, it is recommended that the applications wait for the AE and AWB to
+ * be converged before using the returned shading map data.</p>
*
- * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE
- * @see CaptureResult#STATISTICS_LENS_SHADING_MAP
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_AWB_MODE
* @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
* @see #SHADING_MODE_OFF
* @see #SHADING_MODE_FAST
@@ -1318,10 +1507,8 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* <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
+ * 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
*/
@@ -1332,10 +1519,10 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* <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>
+ * <p>See android.tonemap.curveRed for more details.</p>
*
- * @see CaptureRequest#TONEMAP_CURVE_RED
* @see CaptureRequest#TONEMAP_MODE
+ * @hide
*/
public static final Key<float[]> TONEMAP_CURVE_BLUE =
new Key<float[]>("android.tonemap.curveBlue", float[].class);
@@ -1344,10 +1531,10 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* <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>
+ * <p>See android.tonemap.curveRed for more details.</p>
*
- * @see CaptureRequest#TONEMAP_CURVE_RED
* @see CaptureRequest#TONEMAP_MODE
+ * @hide
*/
public static final Key<float[]> TONEMAP_CURVE_GREEN =
new Key<float[]>("android.tonemap.curveGreen", float[].class);
@@ -1357,7 +1544,7 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* 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} =
+ * <pre><code>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
@@ -1373,15 +1560,15 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* 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 ]
+ * <pre><code>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 ]
+ * <pre><code>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} = [
+ * <pre><code>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,
@@ -1389,7 +1576,7 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* </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} = [
+ * <pre><code>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,
@@ -1397,14 +1584,67 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* </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
+ * @hide
*/
public static final Key<float[]> TONEMAP_CURVE_RED =
new Key<float[]>("android.tonemap.curveRed", float[].class);
/**
+ * <p>Tonemapping / contrast / gamma curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode}
+ * is CONTRAST_CURVE.</p>
+ * <p>The tonemapCurve consist of three curves for each of red, green, and blue
+ * channels respectively. The following example uses the red channel as an
+ * example. The same logic applies to green and blue channel.
+ * Each channel's curve is defined by an array of control points:</p>
+ * <pre><code>curveRed =
+ * [ P0(in, out), P1(in, out), P2(in, out), P3(in, out), ..., PN(in, out) ]
+ * 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>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>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>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>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 CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
+ * @see CaptureRequest#TONEMAP_MODE
+ */
+ public static final Key<android.hardware.camera2.params.TonemapCurve> TONEMAP_CURVE =
+ new Key<android.hardware.camera2.params.TonemapCurve>("android.tonemap.curve", android.hardware.camera2.params.TonemapCurve.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
@@ -1420,18 +1660,15 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* <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}.
+ * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.
* 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
+ * <p>If a request is sent with CONTRAST_CURVE 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_CURVE
* @see CaptureRequest#TONEMAP_MODE
* @see #TONEMAP_MODE_CONTRAST_CURVE
* @see #TONEMAP_MODE_FAST
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 51ea447..7d07c92 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -17,11 +17,16 @@
package android.hardware.camera2;
import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.utils.TypeReference;
+import android.util.Log;
+import android.util.Rational;
+
+import java.util.List;
/**
- * <p>The results of a single image capture from the image sensor.</p>
+ * <p>The subset of the results of a single image capture from the image sensor.</p>
*
- * <p>Contains the final configuration for the capture hardware (sensor, lens,
+ * <p>Contains a subset of the final configuration for the capture hardware (sensor, lens,
* flash), the processing pipeline, the control algorithms, and the output
* buffers.</p>
*
@@ -31,8 +36,106 @@ import android.hardware.camera2.impl.CameraMetadataNative;
* capture. The result also includes additional metadata about the state of the
* camera device during the capture.</p>
*
+ * <p>Not all properties returned by {@link CameraCharacteristics#getAvailableCaptureResultKeys()}
+ * are necessarily available. Some results are {@link CaptureResult partial} and will
+ * not have every key set. Only {@link TotalCaptureResult total} results are guaranteed to have
+ * every key available that was enabled by the request.</p>
+ *
+ * <p>{@link CaptureResult} objects are immutable.</p>
+ *
*/
-public final class CaptureResult extends CameraMetadata {
+public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
+
+ private static final String TAG = "CaptureResult";
+ private static final boolean VERBOSE = false;
+
+ /**
+ * A {@code Key} is used to do capture result field lookups with
+ * {@link CaptureResult#get}.
+ *
+ * <p>For example, to get the timestamp corresponding to the exposure of the first row:
+ * <code><pre>
+ * long timestamp = captureResult.get(CaptureResult.SENSOR_TIMESTAMP);
+ * </pre></code>
+ * </p>
+ *
+ * <p>To enumerate over all possible keys for {@link CaptureResult}, see
+ * {@link CameraCharacteristics#getAvailableCaptureResultKeys}.</p>
+ *
+ * @see CaptureResult#get
+ * @see CameraCharacteristics#getAvailableCaptureResultKeys
+ */
+ public final static class Key<T> {
+ private final CameraMetadataNative.Key<T> mKey;
+
+ /**
+ * Visible for testing and vendor extensions only.
+ *
+ * @hide
+ */
+ public Key(String name, Class<T> type) {
+ mKey = new CameraMetadataNative.Key<T>(name, type);
+ }
+
+ /**
+ * Visible for testing and vendor extensions only.
+ *
+ * @hide
+ */
+ public Key(String name, TypeReference<T> typeReference) {
+ mKey = new CameraMetadataNative.Key<T>(name, typeReference);
+ }
+
+ /**
+ * Return a camelCase, period separated name formatted like:
+ * {@code "root.section[.subsections].name"}.
+ *
+ * <p>Built-in keys exposed by the Android SDK are always prefixed with {@code "android."};
+ * keys that are device/platform-specific are prefixed with {@code "com."}.</p>
+ *
+ * <p>For example, {@code CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP} would
+ * have a name of {@code "android.scaler.streamConfigurationMap"}; whereas a device
+ * specific key might look like {@code "com.google.nexus.data.private"}.</p>
+ *
+ * @return String representation of the key name
+ */
+ public String getName() {
+ return mKey.getName();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final int hashCode() {
+ return mKey.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public final boolean equals(Object o) {
+ return o instanceof Key && ((Key<T>)o).mKey.equals(mKey);
+ }
+
+ /**
+ * Visible for CameraMetadataNative implementation only; do not use.
+ *
+ * TODO: Make this private or remove it altogether.
+ *
+ * @hide
+ */
+ public CameraMetadataNative.Key<T> getNativeKey() {
+ return mKey;
+ }
+
+ @SuppressWarnings({ "unchecked" })
+ /*package*/ Key(CameraMetadataNative.Key<?> nativeKey) {
+ mKey = (CameraMetadataNative.Key<T>) nativeKey;
+ }
+ }
private final CameraMetadataNative mResults;
private final CaptureRequest mRequest;
@@ -51,31 +154,119 @@ public final class CaptureResult extends CameraMetadata {
throw new IllegalArgumentException("parent was null");
}
- mResults = results;
+ mResults = CameraMetadataNative.move(results);
+ if (mResults.isEmpty()) {
+ throw new AssertionError("Results must not be empty");
+ }
mRequest = parent;
mSequenceId = sequenceId;
}
- @Override
+ /**
+ * Returns a copy of the underlying {@link CameraMetadataNative}.
+ * @hide
+ */
+ public CameraMetadataNative getNativeCopy() {
+ return new CameraMetadataNative(mResults);
+ }
+
+ /**
+ * Creates a request-less result.
+ *
+ * <p><strong>For testing only.</strong></p>
+ * @hide
+ */
+ public CaptureResult(CameraMetadataNative results, int sequenceId) {
+ if (results == null) {
+ throw new IllegalArgumentException("results was null");
+ }
+
+ mResults = CameraMetadataNative.move(results);
+ if (mResults.isEmpty()) {
+ throw new AssertionError("Results must not be empty");
+ }
+
+ mRequest = null;
+ mSequenceId = sequenceId;
+ }
+
+ /**
+ * Get a capture result field value.
+ *
+ * <p>The field definitions can be found in {@link CaptureResult}.</p>
+ *
+ * <p>Querying the value for the same key more than once will return a value
+ * which is equal to the previous queried value.</p>
+ *
+ * @throws IllegalArgumentException if the key was not valid
+ *
+ * @param key The result field to read.
+ * @return The value of that key, or {@code null} if the field is not set.
+ */
public <T> T get(Key<T> key) {
- return mResults.get(key);
+ T value = mResults.get(key);
+ if (VERBOSE) Log.v(TAG, "#get for Key = " + key.getName() + ", returned value = " + value);
+ return value;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ protected <T> T getProtected(Key<?> key) {
+ return (T) mResults.get(key);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ protected Class<Key<?>> getKeyClass() {
+ Object thisClass = Key.class;
+ return (Class<Key<?>>)thisClass;
+ }
+
+ /**
+ * Dumps the native metadata contents to logcat.
+ *
+ * <p>Visibility for testing/debugging only. The results will not
+ * include any synthesized keys, as they are invisible to the native layer.</p>
+ *
+ * @hide
+ */
+ public void dumpToLog() {
+ mResults.dumpToLog();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Key<?>> getKeys() {
+ // Force the javadoc for this function to show up on the CaptureResult page
+ return super.getKeys();
}
/**
* Get the request associated with this result.
*
- * <p>Whenever a request is successfully captured, with
- * {@link CameraDevice.CaptureListener#onCaptureCompleted},
- * the {@code result}'s {@code getRequest()} will return that {@code request}.
+ * <p>Whenever a request has been fully or partially captured, with
+ * {@link CameraDevice.CaptureListener#onCaptureCompleted} or
+ * {@link CameraDevice.CaptureListener#onCaptureProgressed}, the {@code result}'s
+ * {@code getRequest()} will return that {@code request}.
* </p>
*
- * <p>In particular,
+ * <p>For example,
* <code><pre>cameraDevice.capture(someRequest, new CaptureListener() {
* {@literal @}Override
* void onCaptureCompleted(CaptureRequest myRequest, CaptureResult myResult) {
* assert(myResult.getRequest.equals(myRequest) == true);
* }
- * };
+ * }, null);
* </code></pre>
* </p>
*
@@ -98,6 +289,7 @@ public final class CaptureResult extends CameraMetadata {
* @return int frame number
*/
public int getFrameNumber() {
+ // TODO: @hide REQUEST_FRAME_COUNT
return get(REQUEST_FRAME_COUNT);
}
@@ -111,6 +303,7 @@ public final class CaptureResult extends CameraMetadata {
* @return int The ID for the sequence of requests that this capture result is a part of
*
* @see CameraDevice.CaptureListener#onCaptureSequenceCompleted
+ * @see CameraDevice.CaptureListener#onCaptureSequenceAborted
*/
public int getSequenceId() {
return mSequenceId;
@@ -190,42 +383,24 @@ public final class CaptureResult extends CameraMetadata {
*
* @see CaptureRequest#COLOR_CORRECTION_MODE
*/
- public static final Key<Rational[]> COLOR_CORRECTION_TRANSFORM =
- new Key<Rational[]>("android.colorCorrection.transform", Rational[].class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> COLOR_CORRECTION_TRANSFORM =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.colorCorrection.transform", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <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 <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
- * {@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>
+ * <p>These per-channel gains are 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 {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is
+ * TRANSFORM_MATRIX.</p>
+ * <p>The gains in the result metadata are 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
- * CAMERA2_TRIGGER_PRECAPTURE_METERING trigger received yet
- * by HAL. Always updated even if AE algorithm ignores the
- * trigger</p>
- * @hide
- */
- public static final Key<Integer> CONTROL_AE_PRECAPTURE_ID =
- new Key<Integer>("android.control.aePrecaptureId", int.class);
+ public static final Key<android.hardware.camera2.params.RggbChannelVector> COLOR_CORRECTION_GAINS =
+ new Key<android.hardware.camera2.params.RggbChannelVector>("android.colorCorrection.gains", android.hardware.camera2.params.RggbChannelVector.class);
/**
* <p>The desired setting for the camera device's auto-exposure
@@ -275,7 +450,19 @@ public final class CaptureResult extends CameraMetadata {
* 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 EV. Note that this control will only be effective
+ * if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} <code>!=</code> OFF. This control will take effect even when
+ * {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} <code>== true</code>.</p>
+ * <p>In the event of exposure compensation value being changed, camera device
+ * may take several frames to reach the newly requested exposure target.
+ * During that time, {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} field will be in the SEARCHING
+ * state. Once the new exposure target is reached, {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} will
+ * change from SEARCHING to either CONVERGED, LOCKED (if AE lock is enabled), or
+ * FLASH_REQUIRED (if the scene is too dark for still capture).</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_LOCK
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureResult#CONTROL_AE_STATE
*/
public static final Key<Integer> CONTROL_AE_EXPOSURE_COMPENSATION =
new Key<Integer>("android.control.aeExposureCompensation", int.class);
@@ -286,6 +473,8 @@ public final class CaptureResult extends CameraMetadata {
* <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>When {@link CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION android.control.aeExposureCompensation} is changed, even if the AE lock
+ * is ON, the camera device will still adjust its exposure value.</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})
@@ -294,6 +483,7 @@ public final class CaptureResult extends CameraMetadata {
* {@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_EXPOSURE_COMPENSATION
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
* @see CaptureResult#CONTROL_AE_STATE
@@ -343,26 +533,27 @@ public final class CaptureResult extends CameraMetadata {
/**
* <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
* ({@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>
+ * bottom-right pixel in the active pixel array.</p>
+ * <p>The weight must range from 0 to 1000, and represents a weight
+ * for every pixel in the area. This means that a large metering area
+ * with the same weight as a smaller area will have more effect in
+ * the metering result. Metering areas can partially overlap and the
+ * camera device will add the weights in the overlap region.</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>
+ * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata,
+ * the camera device will ignore the sections outside the region and output the
+ * used sections in the result 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);
+ public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AE_REGIONS =
+ new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.aeRegions", android.hardware.camera2.params.MeteringRectangle[].class);
/**
* <p>Range over which fps can be adjusted to
@@ -372,8 +563,8 @@ public final class CaptureResult extends CameraMetadata {
*
* @see CaptureRequest#SENSOR_EXPOSURE_TIME
*/
- public static final Key<int[]> CONTROL_AE_TARGET_FPS_RANGE =
- new Key<int[]>("android.control.aeTargetFpsRange", int[].class);
+ public static final Key<android.util.Range<Integer>> CONTROL_AE_TARGET_FPS_RANGE =
+ new Key<android.util.Range<Integer>>("android.control.aeTargetFpsRange", new TypeReference<android.util.Range<Integer>>() {{ }});
/**
* <p>Whether the camera device will trigger a precapture
@@ -616,26 +807,27 @@ public final class CaptureResult extends CameraMetadata {
/**
* <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
* ({@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 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>
+ * bottom-right pixel in the active pixel array.</p>
+ * <p>The weight must range from 0 to 1000, and represents a weight
+ * for every pixel in the area. This means that a large metering area
+ * with the same weight as a smaller area will have more effect in
+ * the metering result. Metering areas can partially overlap and the
+ * camera device will add the weights in the overlap region.</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 used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata,
+ * the camera device will ignore the sections outside the region and output the
+ * used sections in the result 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);
+ public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AF_REGIONS =
+ new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.afRegions", android.hardware.camera2.params.MeteringRectangle[].class);
/**
* <p>Whether the camera device will trigger autofocus for this request.</p>
@@ -1053,17 +1245,6 @@ 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
- * received yet by HAL. Always updated even if AF algorithm
- * 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 locked to its
* latest calculated values.</p>
* <p>Note that AWB lock is only meaningful for AUTO
@@ -1110,27 +1291,27 @@ public final class CaptureResult extends CameraMetadata {
/**
* <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
* ({@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 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>
+ * bottom-right pixel in the active pixel array.</p>
+ * <p>The weight must range from 0 to 1000, and represents a weight
+ * for every pixel in the area. This means that a large metering area
+ * with the same weight as a smaller area will have more effect in
+ * the metering result. Metering areas can partially overlap and the
+ * camera device will add the weights in the overlap region.</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 used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata,
+ * the camera device will ignore the sections outside the region and output the
+ * used sections in the result 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);
+ public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS =
+ new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.awbRegions", android.hardware.camera2.params.MeteringRectangle[].class);
/**
* <p>Information to the camera device 3A (auto-exposure,
@@ -1471,8 +1652,15 @@ public final class CaptureResult extends CameraMetadata {
new Key<Integer>("android.hotPixel.mode", int.class);
/**
+ * <p>A location object to use when generating image GPS metadata.</p>
+ */
+ public static final Key<android.location.Location> JPEG_GPS_LOCATION =
+ new Key<android.location.Location>("android.jpeg.gpsLocation", android.location.Location.class);
+
+ /**
* <p>GPS coordinates to include in output JPEG
* EXIF</p>
+ * @hide
*/
public static final Key<double[]> JPEG_GPS_COORDINATES =
new Key<double[]>("android.jpeg.gpsCoordinates", double[].class);
@@ -1480,6 +1668,7 @@ public final class CaptureResult extends CameraMetadata {
/**
* <p>32 characters describing GPS algorithm to
* include in EXIF</p>
+ * @hide
*/
public static final Key<String> JPEG_GPS_PROCESSING_METHOD =
new Key<String>("android.jpeg.gpsProcessingMethod", String.class);
@@ -1487,6 +1676,7 @@ public final class CaptureResult extends CameraMetadata {
/**
* <p>Time GPS fix was made to include in
* EXIF</p>
+ * @hide
*/
public static final Key<Long> JPEG_GPS_TIMESTAMP =
new Key<Long>("android.jpeg.gpsTimestamp", long.class);
@@ -1519,9 +1709,15 @@ public final class CaptureResult extends CameraMetadata {
* 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>
+ * <p>If the thumbnail image aspect ratio differs from the JPEG primary image aspect
+ * ratio, the camera device creates the thumbnail by cropping it from the primary image.
+ * For example, if the primary image has 4:3 aspect ratio, the thumbnail image has
+ * 16:9 aspect ratio, the primary image will be cropped vertically (letterbox) to
+ * generate the thumbnail image. The thumbnail image will always have a smaller Field
+ * Of View (FOV) than the primary image when aspect ratios differ.</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);
+ public static final Key<android.util.Size> JPEG_THUMBNAIL_SIZE =
+ new Key<android.util.Size>("android.jpeg.thumbnailSize", android.util.Size.class);
/**
* <p>The ratio of lens focal length to the effective
@@ -1608,8 +1804,8 @@ public final class CaptureResult extends CameraMetadata {
* <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);
+ public static final Key<android.util.Pair<Float,Float>> LENS_FOCUS_RANGE =
+ new Key<android.util.Pair<Float,Float>>("android.lens.focusRange", new TypeReference<android.util.Pair<Float,Float>>() {{ }});
/**
* <p>Sets whether the camera device uses optical image stabilization (OIS)
@@ -1698,8 +1894,10 @@ public final class CaptureResult extends CameraMetadata {
* capture must arrive before the FINAL buffer for that capture. This entry may
* 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>
+ * @deprecated
* @hide
*/
+ @Deprecated
public static final Key<Boolean> QUIRKS_PARTIAL_RESULT =
new Key<Boolean>("android.quirks.partialResult", boolean.class);
@@ -1743,8 +1941,11 @@ public final class CaptureResult extends CameraMetadata {
* 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>The crop region is applied after the RAW to other color space (e.g. YUV)
+ * conversion. Since raw streams (e.g. RAW16) don't have the conversion stage,
+ * it is not croppable. The crop region will be ignored by raw streams.</p>
+ * <p>For non-raw streams, any additional per-stream cropping will
+ * 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
@@ -1819,7 +2020,8 @@ public final class CaptureResult extends CameraMetadata {
* 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.
+ * is provided via the {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} field
+ * using StreamConfigurationMap#getOutputMinFrameDuration(int, Size).
* 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
@@ -1829,7 +2031,8 @@ public final class CaptureResult extends CameraMetadata {
* <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
+ * looking it up in {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} using
+ * StreamConfigurationMap#getOutputMinFrameDuration(int, Size) (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
@@ -1837,7 +2040,8 @@ public final class CaptureResult extends CameraMetadata {
* 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
+ * StreamConfigurationMap#getOutputStallDuration(int,Size) using its
+ * respective size/format), 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>
@@ -1848,10 +2052,9 @@ public final class CaptureResult extends CameraMetadata {
* 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>
+ * StreamConfigurationMap#getOutputStallDuration(int,Size).</p>
*
- * @see CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS
- * @see CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
*/
public static final Key<Long> SENSOR_FRAME_DURATION =
new Key<Long>("android.sensor.frameDuration", long.class);
@@ -1877,21 +2080,6 @@ public final class CaptureResult extends CameraMetadata {
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><b>Optional</b> - This value may be {@code null} on some devices.</p>
- * <p><b>Full capability</b> -
- * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
- * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
- *
- * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
- */
- public static final Key<Float> SENSOR_TEMPERATURE =
- new Key<Float>("android.sensor.temperature", float.class);
-
- /**
* <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
@@ -1984,8 +2172,8 @@ public final class CaptureResult extends CameraMetadata {
* <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
+ * shading map with size specified as <code>android.lens.info.shadingMapSize = [ 4, 3 ]</code>,
+ * the output 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,
@@ -1997,11 +2185,17 @@ public final class CaptureResult extends CameraMetadata {
* <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>
+ * lens shading map data in android.statistics.lensShadingMap, with size specified
+ * by android.lens.info.shadingMapSize; the returned shading map data will be the one
+ * applied by the camera device for this capture request.</p>
+ * <p>The shading map data may depend on the AE and AWB statistics, therefore the reliability
+ * of the map data may be affected by the AE and AWB algorithms. When AE and AWB are in
+ * AUTO modes({@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} <code>!=</code> OFF and {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} <code>!=</code> OFF),
+ * to get best results, it is recommended that the applications wait for the AE and AWB to
+ * be converged before using the returned shading map data.</p>
*
- * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE
- * @see CaptureResult#STATISTICS_LENS_SHADING_MAP
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_AWB_MODE
* @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
* @see #SHADING_MODE_OFF
* @see #SHADING_MODE_FAST
@@ -2064,6 +2258,62 @@ public final class CaptureResult extends CameraMetadata {
new Key<byte[]>("android.statistics.faceScores", byte[].class);
/**
+ * <p>List of the faces detected through camera face detection
+ * in this result.</p>
+ * <p>Only available if {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE android.statistics.faceDetectMode} <code>!=</code> OFF.</p>
+ *
+ * @see CaptureRequest#STATISTICS_FACE_DETECT_MODE
+ */
+ public static final Key<android.hardware.camera2.params.Face[]> STATISTICS_FACES =
+ new Key<android.hardware.camera2.params.Face[]>("android.statistics.faces", android.hardware.camera2.params.Face[].class);
+
+ /**
+ * <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.</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>width,height = [ 4, 3 ]
+ * values =
+ * [ 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
+ */
+ public static final Key<android.hardware.camera2.params.LensShadingMap> STATISTICS_LENS_SHADING_CORRECTION_MAP =
+ new Key<android.hardware.camera2.params.LensShadingMap>("android.statistics.lensShadingCorrectionMap", android.hardware.camera2.params.LensShadingMap.class);
+
+ /**
* <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>
@@ -2082,12 +2332,12 @@ public final class CaptureResult extends CameraMetadata {
* <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>
+ * is provided in the camera static metadata by 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} =
+ * <pre><code>android.lens.info.shadingMapSize = [ 4, 3 ]
+ * 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,
@@ -2106,8 +2356,7 @@ public final class CaptureResult extends CameraMetadata {
* <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
+ * @hide
*/
public static final Key<float[]> STATISTICS_LENS_SHADING_MAP =
new Key<float[]>("android.statistics.lensShadingMap", float[].class);
@@ -2126,8 +2375,10 @@ public final class CaptureResult extends CameraMetadata {
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
*
* @see CaptureRequest#COLOR_CORRECTION_GAINS
+ * @deprecated
* @hide
*/
+ @Deprecated
public static final Key<float[]> STATISTICS_PREDICTED_COLOR_GAINS =
new Key<float[]>("android.statistics.predictedColorGains", float[].class);
@@ -2148,8 +2399,10 @@ public final class CaptureResult extends CameraMetadata {
* <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>
+ * @deprecated
* @hide
*/
+ @Deprecated
public static final Key<Rational[]> STATISTICS_PREDICTED_COLOR_TRANSFORM =
new Key<Rational[]>("android.statistics.predictedColorTransform", Rational[].class);
@@ -2203,17 +2456,15 @@ public final class CaptureResult extends CameraMetadata {
* @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);
+ public static final Key<android.graphics.Point[]> STATISTICS_HOT_PIXEL_MAP =
+ new Key<android.graphics.Point[]>("android.statistics.hotPixelMap", android.graphics.Point[].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
+ * 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
*/
@@ -2224,10 +2475,10 @@ public final class CaptureResult extends CameraMetadata {
* <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>
+ * <p>See android.tonemap.curveRed for more details.</p>
*
- * @see CaptureRequest#TONEMAP_CURVE_RED
* @see CaptureRequest#TONEMAP_MODE
+ * @hide
*/
public static final Key<float[]> TONEMAP_CURVE_BLUE =
new Key<float[]>("android.tonemap.curveBlue", float[].class);
@@ -2236,10 +2487,10 @@ public final class CaptureResult extends CameraMetadata {
* <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>
+ * <p>See android.tonemap.curveRed for more details.</p>
*
- * @see CaptureRequest#TONEMAP_CURVE_RED
* @see CaptureRequest#TONEMAP_MODE
+ * @hide
*/
public static final Key<float[]> TONEMAP_CURVE_GREEN =
new Key<float[]>("android.tonemap.curveGreen", float[].class);
@@ -2249,7 +2500,7 @@ public final class CaptureResult extends CameraMetadata {
* 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} =
+ * <pre><code>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
@@ -2265,15 +2516,15 @@ public final class CaptureResult extends CameraMetadata {
* 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 ]
+ * <pre><code>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 ]
+ * <pre><code>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} = [
+ * <pre><code>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,
@@ -2281,7 +2532,7 @@ public final class CaptureResult extends CameraMetadata {
* </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} = [
+ * <pre><code>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,
@@ -2289,14 +2540,67 @@ public final class CaptureResult extends CameraMetadata {
* </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
+ * @hide
*/
public static final Key<float[]> TONEMAP_CURVE_RED =
new Key<float[]>("android.tonemap.curveRed", float[].class);
/**
+ * <p>Tonemapping / contrast / gamma curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode}
+ * is CONTRAST_CURVE.</p>
+ * <p>The tonemapCurve consist of three curves for each of red, green, and blue
+ * channels respectively. The following example uses the red channel as an
+ * example. The same logic applies to green and blue channel.
+ * Each channel's curve is defined by an array of control points:</p>
+ * <pre><code>curveRed =
+ * [ P0(in, out), P1(in, out), P2(in, out), P3(in, out), ..., PN(in, out) ]
+ * 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>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>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>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>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 CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
+ * @see CaptureRequest#TONEMAP_MODE
+ */
+ public static final Key<android.hardware.camera2.params.TonemapCurve> TONEMAP_CURVE =
+ new Key<android.hardware.camera2.params.TonemapCurve>("android.tonemap.curve", android.hardware.camera2.params.TonemapCurve.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
@@ -2312,18 +2616,15 @@ public final class CaptureResult extends CameraMetadata {
* <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}.
+ * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.
* 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
+ * <p>If a request is sent with CONTRAST_CURVE 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_CURVE
* @see CaptureRequest#TONEMAP_MODE
* @see #TONEMAP_MODE_CONTRAST_CURVE
* @see #TONEMAP_MODE_FAST
@@ -2425,19 +2726,4 @@ public final class CaptureResult extends CameraMetadata {
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
-
- /**
- * <p>
- * List of the {@link Face Faces} detected through camera face detection
- * in this result.
- * </p>
- * <p>
- * Only available if {@link #STATISTICS_FACE_DETECT_MODE} {@code !=}
- * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_OFF OFF}.
- * </p>
- *
- * @see Face
- */
- public static final Key<Face[]> STATISTICS_FACES =
- new Key<Face[]>("android.statistics.faces", Face[].class);
}
diff --git a/core/java/android/hardware/camera2/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java
new file mode 100644
index 0000000..54568ed
--- /dev/null
+++ b/core/java/android/hardware/camera2/DngCreator.java
@@ -0,0 +1,368 @@
+/*
+ * 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.hardware.camera2;
+
+import android.graphics.Bitmap;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.location.Location;
+import android.media.ExifInterface;
+import android.media.Image;
+import android.util.Size;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * The {@link DngCreator} class provides functions to write raw pixel data as a DNG file.
+ *
+ * <p>
+ * This class is designed to be used with the {@link android.graphics.ImageFormat#RAW_SENSOR}
+ * buffers available from {@link android.hardware.camera2.CameraDevice}, or with Bayer-type raw
+ * pixel data that is otherwise generated by an application. The DNG metadata tags will be
+ * generated from a {@link android.hardware.camera2.CaptureResult} object or set directly.
+ * </p>
+ *
+ * <p>
+ * The DNG file format is a cross-platform file format that is used to store pixel data from
+ * camera sensors with minimal pre-processing applied. DNG files allow for pixel data to be
+ * defined in a user-defined colorspace, and have associated metadata that allow for this
+ * pixel data to be converted to the standard CIE XYZ colorspace during post-processing.
+ * </p>
+ *
+ * <p>
+ * For more information on the DNG file format and associated metadata, please refer to the
+ * <a href=
+ * "https://wwwimages2.adobe.com/content/dam/Adobe/en/products/photoshop/pdfs/dng_spec_1.4.0.0.pdf">
+ * Adobe DNG 1.4.0.0 specification</a>.
+ * </p>
+ */
+public final class DngCreator implements AutoCloseable {
+
+ /**
+ * Create a new DNG object.
+ *
+ * <p>
+ * It is not necessary to call any set methods to write a well-formatted DNG file.
+ * </p>
+ * <p>
+ * DNG metadata tags will be generated from the corresponding parameters in the
+ * {@link android.hardware.camera2.CaptureResult} object. This removes or overrides
+ * all previous tags set.
+ * </p>
+ *
+ * @param characteristics an object containing the static
+ * {@link android.hardware.camera2.CameraCharacteristics}.
+ * @param metadata a metadata object to generate tags from.
+ */
+ public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) {
+ if (characteristics == null || metadata == null) {
+ throw new NullPointerException("Null argument to DngCreator constructor");
+ }
+ nativeInit(characteristics.getNativeCopy(), metadata.getNativeCopy());
+ }
+
+ /**
+ * Set the orientation value to write.
+ *
+ * <p>
+ * This will be written as the TIFF "Orientation" tag {@code (0x0112)}.
+ * Calling this will override any prior settings for this tag.
+ * </p>
+ *
+ * @param orientation the orientation value to set, one of:
+ * <ul>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_NORMAL}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_HORIZONTAL}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_180}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_VERTICAL}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_TRANSPOSE}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_90}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_TRANSVERSE}</li>
+ * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_270}</li>
+ * </ul>
+ * @return this {@link #DngCreator} object.
+ */
+ public DngCreator setOrientation(int orientation) {
+
+ if (orientation < ExifInterface.ORIENTATION_UNDEFINED ||
+ orientation > ExifInterface.ORIENTATION_ROTATE_270) {
+ throw new IllegalArgumentException("Orientation " + orientation +
+ " is not a valid EXIF orientation value");
+ }
+ nativeSetOrientation(orientation);
+ return this;
+ }
+
+ /**
+ * Set the thumbnail image.
+ *
+ * <p>
+ * Pixel data will be converted to a Baseline TIFF RGB image, with 8 bits per color channel.
+ * The alpha channel will be discarded.
+ * </p>
+ *
+ * <p>
+ * The given bitmap should not be altered while this object is in use.
+ * </p>
+ *
+ * @param pixels a {@link android.graphics.Bitmap} of pixel data.
+ * @return this {@link #DngCreator} object.
+ */
+ public DngCreator setThumbnail(Bitmap pixels) {
+ if (pixels == null) {
+ throw new NullPointerException("Null argument to setThumbnail");
+ }
+
+ Bitmap.Config config = pixels.getConfig();
+
+ if (config != Bitmap.Config.ARGB_8888) {
+ pixels = pixels.copy(Bitmap.Config.ARGB_8888, false);
+ if (pixels == null) {
+ throw new IllegalArgumentException("Unsupported Bitmap format " + config);
+ }
+ nativeSetThumbnailBitmap(pixels);
+ }
+
+ return this;
+ }
+
+ /**
+ * Set the thumbnail image.
+ *
+ * <p>
+ * Pixel data is interpreted as a {@link android.graphics.ImageFormat#YUV_420_888} image.
+ * </p>
+ *
+ * <p>
+ * The given image should not be altered while this object is in use.
+ * </p>
+ *
+ * @param pixels an {@link android.media.Image} object with the format
+ * {@link android.graphics.ImageFormat#YUV_420_888}.
+ * @return this {@link #DngCreator} object.
+ */
+ public DngCreator setThumbnail(Image pixels) {
+ if (pixels == null) {
+ throw new NullPointerException("Null argument to setThumbnail");
+ }
+
+ int format = pixels.getFormat();
+ if (format != ImageFormat.YUV_420_888) {
+ throw new IllegalArgumentException("Unsupported image format " + format);
+ }
+
+ Image.Plane[] planes = pixels.getPlanes();
+ nativeSetThumbnailImage(pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(),
+ planes[0].getRowStride(), planes[0].getPixelStride(), planes[1].getBuffer(),
+ planes[1].getRowStride(), planes[1].getPixelStride(), planes[1].getBuffer(),
+ planes[1].getRowStride(), planes[1].getPixelStride());
+
+ return this;
+ }
+
+
+ /**
+ * Set image location metadata.
+ *
+ * <p>
+ * The given location object must contain at least a valid time, latitude, and longitude
+ * (equivalent to the values returned by {@link android.location.Location#getTime()},
+ * {@link android.location.Location#getLatitude()}, and
+ * {@link android.location.Location#getLongitude()} methods).
+ * </p>
+ *
+ * @param location an {@link android.location.Location} object to set.
+ * @return this {@link #DngCreator} object.
+ *
+ * @throws java.lang.IllegalArgumentException if the given location object doesn't
+ * contain enough information to set location metadata.
+ */
+ public DngCreator setLocation(Location location) {
+ /*TODO*/
+ return this;
+ }
+
+ /**
+ * Set the user description string to write.
+ *
+ * <p>
+ * This is equivalent to setting the TIFF "ImageDescription" tag {@code (0x010E)}.
+ * </p>
+ *
+ * @param description the user description string.
+ * @return this {@link #DngCreator} object.
+ */
+ public DngCreator setDescription(String description) {
+ /*TODO*/
+ return this;
+ }
+
+ /**
+ * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with
+ * the currently configured metadata.
+ *
+ * <p>
+ * Raw pixel data must have 16 bits per pixel, and the input must contain at least
+ * {@code offset + 2 * width * height)} bytes. The width and height of
+ * the input are taken from the width and height set in the {@link DngCreator} metadata tags,
+ * and will typically be equal to the width and height of
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
+ * The pixel layout in the input is determined from the reported color filter arrangement (CFA)
+ * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}. If insufficient
+ * metadata is available to write a well-formatted DNG file, an
+ * {@link java.lang.IllegalStateException} will be thrown.
+ * </p>
+ *
+ * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
+ * @param size the {@link Size} of the image to write, in pixels.
+ * @param pixels an {@link java.io.InputStream} of pixel data to write.
+ * @param offset the offset of the raw image in bytes. This indicates how many bytes will
+ * be skipped in the input before any pixel data is read.
+ *
+ * @throws IOException if an error was encountered in the input or output stream.
+ * @throws java.lang.IllegalStateException if not enough metadata information has been
+ * set to write a well-formatted DNG file.
+ * @throws java.lang.IllegalArgumentException if the size passed in does not match the
+ */
+ public void writeInputStream(OutputStream dngOutput, Size size, InputStream pixels, long offset)
+ throws IOException {
+ if (dngOutput == null || pixels == null) {
+ throw new NullPointerException("Null argument to writeImage");
+ }
+ nativeWriteInputStream(dngOutput, pixels, offset);
+ }
+
+ /**
+ * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with
+ * the currently configured metadata.
+ *
+ * <p>
+ * Raw pixel data must have 16 bits per pixel, and the input must contain at least
+ * {@code offset + 2 * width * height)} bytes. The width and height of
+ * the input are taken from the width and height set in the {@link DngCreator} metadata tags,
+ * and will typically be equal to the width and height of
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
+ * The pixel layout in the input is determined from the reported color filter arrangement (CFA)
+ * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}. If insufficient
+ * metadata is available to write a well-formatted DNG file, an
+ * {@link java.lang.IllegalStateException} will be thrown.
+ * </p>
+ *
+ * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
+ * @param size the {@link Size} of the image to write, in pixels.
+ * @param pixels an {@link java.nio.ByteBuffer} of pixel data to write.
+ * @param offset the offset of the raw image in bytes. This indicates how many bytes will
+ * be skipped in the input before any pixel data is read.
+ *
+ * @throws IOException if an error was encountered in the input or output stream.
+ * @throws java.lang.IllegalStateException if not enough metadata information has been
+ * set to write a well-formatted DNG file.
+ */
+ public void writeByteBuffer(OutputStream dngOutput, Size size, ByteBuffer pixels, long offset)
+ throws IOException {
+ if (dngOutput == null || pixels == null) {
+ throw new NullPointerException("Null argument to writeImage");
+ }
+ nativeWriteByteBuffer(dngOutput, pixels, offset);
+ }
+
+ /**
+ * Write the pixel data to a DNG file with the currently configured metadata.
+ *
+ * <p>
+ * For this method to succeed, the {@link android.media.Image} input must contain
+ * {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data, otherwise an
+ * {@link java.lang.IllegalArgumentException} will be thrown.
+ * </p>
+ *
+ * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
+ * @param pixels an {@link android.media.Image} to write.
+ *
+ * @throws java.io.IOException if an error was encountered in the output stream.
+ * @throws java.lang.IllegalArgumentException if an image with an unsupported format was used.
+ * @throws java.lang.IllegalStateException if not enough metadata information has been
+ * set to write a well-formatted DNG file.
+ */
+ public void writeImage(OutputStream dngOutput, Image pixels) throws IOException {
+ if (dngOutput == null || pixels == null) {
+ throw new NullPointerException("Null argument to writeImage");
+ }
+
+ int format = pixels.getFormat();
+ if (format != ImageFormat.RAW_SENSOR) {
+ throw new IllegalArgumentException("Unsupported image format " + format);
+ }
+
+ Image.Plane[] planes = pixels.getPlanes();
+ nativeWriteImage(dngOutput, pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(),
+ planes[0].getRowStride(), planes[0].getPixelStride());
+ }
+
+ @Override
+ public void close() {
+ nativeDestroy();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * This field is used by native code, do not access or modify.
+ */
+ private long mNativeContext;
+
+ private static native void nativeClassInit();
+
+ private synchronized native void nativeInit(CameraMetadataNative nativeCharacteristics,
+ CameraMetadataNative nativeResult);
+
+ private synchronized native void nativeDestroy();
+
+ private synchronized native void nativeSetOrientation(int orientation);
+
+ private synchronized native void nativeSetThumbnailBitmap(Bitmap bitmap);
+
+ private synchronized native void nativeSetThumbnailImage(int width, int height,
+ ByteBuffer yBuffer, int yRowStride,
+ int yPixStride, ByteBuffer uBuffer,
+ int uRowStride, int uPixStride,
+ ByteBuffer vBuffer, int vRowStride,
+ int vPixStride);
+
+ private synchronized native void nativeWriteImage(OutputStream out, int width, int height,
+ ByteBuffer rawBuffer, int rowStride,
+ int pixStride) throws IOException;
+
+ private synchronized native void nativeWriteByteBuffer(OutputStream out, ByteBuffer rawBuffer,
+ long offset) throws IOException;
+
+ private synchronized native void nativeWriteInputStream(OutputStream out, InputStream rawStream,
+ long offset) throws IOException;
+
+ static {
+ nativeClassInit();
+ }
+}
diff --git a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
index a14d38b..caabed3 100644
--- a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
+++ b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
@@ -17,7 +17,7 @@
package android.hardware.camera2;
import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.CaptureResultExtras;
+import android.hardware.camera2.impl.CaptureResultExtras;
/** @hide */
interface ICameraDeviceCallbacks
diff --git a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
index d77f3d1..50a58ed 100644
--- a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
+++ b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
@@ -20,13 +20,14 @@ import android.view.Surface;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.LongParcelable;
+import android.hardware.camera2.utils.LongParcelable;
/** @hide */
interface ICameraDeviceUser
{
/**
- * Keep up-to-date with frameworks/av/include/camera/camera2/ICameraDeviceUser.h
+ * Keep up-to-date with frameworks/av/include/camera/camera2/ICameraDeviceUser.h and
+ * frameworks/base/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
*/
void disconnect();
@@ -41,6 +42,27 @@ interface ICameraDeviceUser
int cancelRequest(int requestId, out LongParcelable lastFrameNumber);
+ /**
+ * Begin the device configuration.
+ *
+ * <p>
+ * beginConfigure must be called before any call to deleteStream, createStream,
+ * or endConfigure. It is not valid to call this when the device is not idle.
+ * <p>
+ */
+ int beginConfigure();
+
+ /**
+ * End the device configuration.
+ *
+ * <p>
+ * endConfigure must be called after stream configuration is complete (i.e. after
+ * a call to beginConfigure and subsequent createStream/deleteStream calls). This
+ * must be called before any requests can be submitted.
+ * <p>
+ */
+ int endConfigure();
+
int deleteStream(int streamId);
// non-negative value is the stream ID. negative value is status_t
diff --git a/core/java/android/hardware/camera2/Size.java b/core/java/android/hardware/camera2/Size.java
deleted file mode 100644
index 9328a003..0000000
--- a/core/java/android/hardware/camera2/Size.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.hardware.camera2;
-
-// TODO: Delete this class, since it was moved to android.util as public API
-
-/**
- * Immutable class for describing width and height dimensions in pixels.
- *
- * @hide
- */
-public final class Size {
- /**
- * Create a new immutable Size instance.
- *
- * @param width The width of the size, in pixels
- * @param height The height of the size, in pixels
- */
- public Size(final int width, final int height) {
- mWidth = width;
- mHeight = height;
- }
-
- /**
- * Get the width of the size (in pixels).
- * @return width
- */
- public final int getWidth() {
- return mWidth;
- }
-
- /**
- * Get the height of the size (in pixels).
- * @return height
- */
- public final int getHeight() {
- return mHeight;
- }
-
- /**
- * Check if this size is equal to another size.
- * <p>
- * Two sizes are equal if and only if both their widths and heights are
- * equal.
- * </p>
- * <p>
- * A size object is never equal to any other type of object.
- * </p>
- *
- * @return {@code true} if the objects were equal, {@code false} otherwise
- */
- @Override
- public boolean equals(final Object obj) {
- if (obj == null) {
- return false;
- }
- if (this == obj) {
- return true;
- }
- if (obj instanceof Size) {
- final Size other = (Size) obj;
- return mWidth == other.mWidth && mHeight == other.mHeight;
- }
- return false;
- }
-
- /**
- * Return the size represented as a string with the format {@code "WxH"}
- *
- * @return string representation of the size
- */
- @Override
- public String toString() {
- return mWidth + "x" + mHeight;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int hashCode() {
- // assuming most sizes are <2^16, doing a rotate will give us perfect hashing
- return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2)));
- }
-
- private final int mWidth;
- private final int mHeight;
-};
diff --git a/core/java/android/hardware/camera2/StreamConfigurationMap.java b/core/java/android/hardware/camera2/StreamConfigurationMap.java
deleted file mode 100644
index e24fd1b..0000000
--- a/core/java/android/hardware/camera2/StreamConfigurationMap.java
+++ /dev/null
@@ -1,508 +0,0 @@
-/*
- * 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.graphics.ImageFormat;
-import android.graphics.PixelFormat;
-import android.hardware.camera2.impl.HashCodeHelpers;
-import android.view.Surface;
-import android.util.Size;
-
-import java.util.Arrays;
-
-import static com.android.internal.util.Preconditions.*;
-
-/**
- * Immutable class to store the available stream
- * {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS configurations} to be used
- * when configuring streams with {@link CameraDevice#configureOutputs}.
- * <!-- TODO: link to input stream configuration -->
- *
- * <p>This is the authoritative list for all <!-- input/ -->output formats (and sizes respectively
- * for that format) that are supported by a camera device.</p>
- *
- * <p>This also contains the minimum frame durations and stall durations for each format/size
- * combination that can be used to calculate effective frame rate when submitting multiple captures.
- * </p>
- *
- * <p>An instance of this object is available from {@link CameraCharacteristics} using
- * the {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS} key and the
- * {@link CameraCharacteristics#get} method.</p.
- *
- * <pre>{@code
- * CameraCharacteristics characteristics = ...;
- * StreamConfigurationMap configs = characteristics.get(
- * CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS);
- * }</pre>
- *
- * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS
- * @see CameraDevice#configureOutputs
- */
-public final class StreamConfigurationMap {
-
- /**
- * Create a new {@link StreamConfigurationMap}.
- *
- * <p>The array parameters ownership is passed to this object after creation; do not
- * write to them after this constructor is invoked.</p>
- *
- * @param configurations a non-{@code null} array of {@link StreamConfiguration}
- * @param durations a non-{@code null} array of {@link StreamConfigurationDuration}
- *
- * @throws NullPointerException if any of the arguments or subelements were {@code null}
- *
- * @hide
- */
- public StreamConfigurationMap(
- StreamConfiguration[] configurations,
- StreamConfigurationDuration[] durations) {
- // TODO: format check against ImageFormat/PixelFormat ?
-
- mConfigurations = checkArrayElementsNotNull(configurations, "configurations");
- mDurations = checkArrayElementsNotNull(durations, "durations");
-
- throw new UnsupportedOperationException("Not implemented yet");
- }
-
- /**
- * Get the image {@code format} output formats in this stream configuration.
- *
- * <p>All image formats returned by this function will be defined in either {@link ImageFormat}
- * or in {@link PixelFormat} (and there is no possibility of collision).</p>
- *
- * <p>Formats listed in this array are guaranteed to return true if queried with
- * {@link #isOutputSupportedFor(int).</p>
- *
- * @return an array of integer format
- *
- * @see ImageFormat
- * @see PixelFormat
- */
- public final int[] getOutputFormats() {
- throw new UnsupportedOperationException("Not implemented yet");
- }
-
- /**
- * Get the image {@code format} input formats in this stream configuration.
- *
- * <p>All image formats returned by this function will be defined in either {@link ImageFormat}
- * or in {@link PixelFormat} (and there is no possibility of collision).</p>
- *
- * @return an array of integer format
- *
- * @see ImageFormat
- * @see PixelFormat
- *
- * @hide
- */
- public final int[] getInputFormats() {
- throw new UnsupportedOperationException("Not implemented yet");
- }
-
- /**
- * Get the supported input sizes for this input format.
- *
- * <p>The format must have come from {@link #getInputFormats}; otherwise
- * {@code null} is returned.</p>
- *
- * @param format a format from {@link #getInputFormats}
- * @return a non-empty array of sizes, or {@code null} if the format was not available.
- *
- * @hide
- */
- public Size[] getInputSizes(final int format) {
- throw new UnsupportedOperationException("Not implemented yet");
- }
-
- /**
- * Determine whether or not output streams can be
- * {@link CameraDevice#configureOutputs configured} with a particular user-defined format.
- *
- * <p>This method determines that the output {@code format} is supported by the camera device;
- * each output {@code surface} target may or may not itself support that {@code format}.
- * Refer to the class which provides the surface for additional documentation.</p>
- *
- * <p>Formats for which this returns {@code true} are guaranteed to exist in the result
- * returned by {@link #getOutputSizes}.</p>
- *
- * @param format an image format from either {@link ImageFormat} or {@link PixelFormat}
- * @return
- * {@code true} iff using a {@code surface} with this {@code format} will be
- * supported with {@link CameraDevice#configureOutputs}
- *
- * @throws IllegalArgumentException
- * if the image format was not a defined named constant
- * from either {@link ImageFormat} or {@link PixelFormat}
- *
- * @see ImageFormat
- * @see PixelFormat
- * @see CameraDevice#configureOutputs
- */
- public boolean isOutputSupportedFor(int format) {
- checkArgumentFormat(format);
-
- final int[] formats = getOutputFormats();
- for (int i = 0; i < formats.length; ++i) {
- if (format == formats[i]) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Determine whether or not output streams can be configured with a particular class
- * as a consumer.
- *
- * <p>The following list is generally usable for outputs:
- * <ul>
- * <li>{@link android.media.ImageReader} -
- * Recommended for image processing or streaming to external resources (such as a file or
- * network)
- * <li>{@link android.media.MediaRecorder} -
- * Recommended for recording video (simple to use)
- * <li>{@link android.media.MediaCodec} -
- * Recommended for recording video (more complicated to use, with more flexibility)
- * <li>{@link android.renderscript.Allocation} -
- * Recommended for image processing with {@link android.renderscript RenderScript}
- * <li>{@link android.view.SurfaceHolder} -
- * Recommended for low-power camera preview with {@link android.view.SurfaceView}
- * <li>{@link android.graphics.SurfaceTexture} -
- * Recommended for OpenGL-accelerated preview processing or compositing with
- * {@link android.view.TextureView}
- * </ul>
- * </p>
- *
- * <p>Generally speaking this means that creating a {@link Surface} from that class <i>may</i>
- * provide a producer endpoint that is suitable to be used with
- * {@link CameraDevice#configureOutputs}.</p>
- *
- * <p>Since not all of the above classes support output of all format and size combinations,
- * the particular combination should be queried with {@link #isOutputSupportedFor(Surface)}.</p>
- *
- * @param klass a non-{@code null} {@link Class} object reference
- * @return {@code true} if this class is supported as an output, {@code false} otherwise
- *
- * @throws NullPointerException if {@code klass} was {@code null}
- *
- * @see CameraDevice#configureOutputs
- * @see #isOutputSupportedFor(Surface)
- */
- public static <T> boolean isOutputSupportedFor(final Class<T> klass) {
- checkNotNull(klass, "klass must not be null");
- throw new UnsupportedOperationException("Not implemented yet");
- }
-
- /**
- * Determine whether or not the {@code surface} in its current state is suitable to be
- * {@link CameraDevice#configureOutputs configured} as an output.
- *
- * <p>Not all surfaces are usable with the {@link CameraDevice}, and not all configurations
- * of that {@code surface} are compatible. Some classes that provide the {@code surface} are
- * compatible with the {@link CameraDevice} in general
- * (see {@link #isOutputSupportedFor(Class)}, but it is the caller's responsibility to put the
- * {@code surface} into a state that will be compatible with the {@link CameraDevice}.</p>
- *
- * <p>Reasons for a {@code surface} being specifically incompatible might be:
- * <ul>
- * <li>Using a format that's not listed by {@link #getOutputFormats}
- * <li>Using a format/size combination that's not listed by {@link #getOutputSizes}
- * <li>The {@code surface} itself is not in a state where it can service a new producer.</p>
- * </li>
- * </ul>
- *
- * This is not an exhaustive list; see the particular class's documentation for further
- * possible reasons of incompatibility.</p>
- *
- * @param surface a non-{@code null} {@link Surface} object reference
- * @return {@code true} if this is supported, {@code false} otherwise
- *
- * @throws NullPointerException if {@code surface} was {@code null}
- *
- * @see CameraDevice#configureOutputs
- * @see #isOutputSupportedFor(Class)
- */
- public boolean isOutputSupportedFor(final Surface surface) {
- checkNotNull(surface, "surface must not be null");
-
- throw new UnsupportedOperationException("Not implemented yet");
- }
-
- /**
- * Get a list of sizes compatible with {@code klass} to use as an output.
- *
- * <p>Since some of the supported classes may support additional formats beyond
- * an opaque/implementation-defined (under-the-hood) format; this function only returns
- * sizes for the implementation-defined format.</p>
- *
- * <p>Some classes such as {@link android.media.ImageReader} may only support user-defined
- * formats; in particular {@link #isOutputSupportedFor(Class)} will return {@code true} for
- * that class and this method will return an empty array (but not {@code null}).</p>
- *
- * <p>If a well-defined format such as {@code NV21} is required, use
- * {@link #getOutputSizes(int)} instead.</p>
- *
- * <p>The {@code klass} should be a supported output, that querying
- * {@code #isOutputSupportedFor(Class)} should return {@code true}.</p>
- *
- * @param klass
- * a non-{@code null} {@link Class} object reference
- * @return
- * an array of supported sizes for implementation-defined formats,
- * or {@code null} iff the {@code klass} is not a supported output
- *
- * @throws NullPointerException if {@code klass} was {@code null}
- *
- * @see #isOutputSupportedFor(Class)
- */
- public <T> Size[] getOutputSizes(final Class<T> klass) {
- throw new UnsupportedOperationException("Not implemented yet");
- }
-
- /**
- * Get a list of sizes compatible with the requested image {@code format}.
- *
- * <p>The {@code format} should be a supported format (one of the formats returned by
- * {@link #getOutputFormats}).</p>
- *
- * @param format an image format from {@link ImageFormat} or {@link PixelFormat}
- * @return
- * an array of supported sizes,
- * or {@code null} if the {@code format} is not a supported output
- *
- * @see ImageFormat
- * @see PixelFormat
- * @see #getOutputFormats
- */
- public Size[] getOutputSizes(final int format) {
- try {
- checkArgumentFormatSupported(format, /*output*/true);
- } catch (IllegalArgumentException e) {
- return null;
- }
-
- throw new UnsupportedOperationException("Not implemented yet");
- }
-
- /**
- * Get the minimum {@link CaptureRequest#SENSOR_FRAME_DURATION frame duration}
- * for the format/size combination (in nanoseconds).
- *
- * <p>{@code format} should be one of the ones returned by {@link #getOutputFormats()}.</p>
- * <p>{@code size} should be one of the ones returned by
- * {@link #getOutputSizes(int)}.</p>
- *
- * @param format an image format from {@link ImageFormat} or {@link PixelFormat}
- * @param size an output-compatible size
- * @return a minimum frame duration {@code >=} 0 in nanoseconds
- *
- * @throws IllegalArgumentException if {@code format} or {@code size} was not supported
- * @throws NullPointerException if {@code size} was {@code null}
- *
- * @see CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS
- * @see CaptureRequest#SENSOR_FRAME_DURATION
- * @see ImageFormat
- * @see PixelFormat
- */
- public long getOutputMinFrameDuration(final int format, final Size size) {
- checkArgumentFormatSupported(format, /*output*/true);
-
- throw new UnsupportedOperationException("Not implemented yet");
- }
-
- /**
- * Get the minimum {@link CaptureRequest#SENSOR_FRAME_DURATION frame duration}
- * for the class/size combination (in nanoseconds).
- *
- * <p>This assumes a the {@code klass} is set up to use an implementation-defined format.
- * For user-defined formats, use {@link #getOutputMinFrameDuration(int, Size)}.</p>
- *
- * <p>{@code klass} should be one of the ones which is supported by
- * {@link #isOutputSupportedFor(Class)}.</p>
- *
- * <p>{@code size} should be one of the ones returned by
- * {@link #getOutputSizes(int)}.</p>
- *
- * @param klass
- * a class which is supported by {@link #isOutputSupportedFor(Class)} and has a
- * non-empty array returned by {@link #getOutputSizes(Class)}
- * @param size an output-compatible size
- * @return a minimum frame duration {@code >=} 0 in nanoseconds
- *
- * @throws IllegalArgumentException if {@code klass} or {@code size} was not supported
- * @throws NullPointerException if {@code size} or {@code klass} was {@code null}
- *
- * @see CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS
- * @see CaptureRequest#SENSOR_FRAME_DURATION
- * @see ImageFormat
- * @see PixelFormat
- */
- public <T> long getOutputMinFrameDuration(final Class<T> klass, final Size size) {
- throw new UnsupportedOperationException("Not implemented yet");
- }
-
- /**
- * Get the {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS stall duration}
- * for the format/size combination (in nanoseconds).
- *
- * <p>{@code format} should be one of the ones returned by {@link #getOutputFormats()}.</p>
- * <p>{@code size} should be one of the ones returned by
- * {@link #getOutputSizes(int)}.</p>
- *
- * @param format an image format from {@link ImageFormat} or {@link PixelFormat}
- * @param size an output-compatible size
- * @return a stall duration {@code >=} 0 in nanoseconds
- *
- * @throws IllegalArgumentException if {@code format} or {@code size} was not supported
- * @throws NullPointerException if {@code size} was {@code null}
- *
- * @see CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS
- * @see ImageFormat
- * @see PixelFormat
- */
- public long getOutputStallDuration(final int format, final Size size) {
- checkArgumentFormatSupported(format, /*output*/true);
- throw new UnsupportedOperationException("Not implemented yet");
- }
-
- /**
- * Get the {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS stall duration}
- * for the class/size combination (in nanoseconds).
- *
- * <p>This assumes a the {@code klass} is set up to use an implementation-defined format.
- * For user-defined formats, use {@link #getOutputMinFrameDuration(int, Size)}.</p>
- *
- * <p>{@code klass} should be one of the ones with a non-empty array returned by
- * {@link #getOutputSizes(Class)}.</p>
- *
- * <p>{@code size} should be one of the ones returned by
- * {@link #getOutputSizes(Class)}.</p>
- *
- * @param klass
- * a class which is supported by {@link #isOutputSupportedFor(Class)} and has a
- * non-empty array returned by {@link #getOutputSizes(Class)}
- * @param size an output-compatible size
- * @return a minimum frame duration {@code >=} 0 in nanoseconds
- *
- * @throws IllegalArgumentException if {@code klass} or {@code size} was not supported
- * @throws NullPointerException if {@code size} or {@code klass} was {@code null}
- *
- * @see CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS
- * @see CaptureRequest#SENSOR_FRAME_DURATION
- * @see ImageFormat
- * @see PixelFormat
- */
- public <T> long getOutputStallDuration(final Class<T> klass, final Size size) {
- throw new UnsupportedOperationException("Not implemented yet");
- }
-
- /**
- * Check if this {@link StreamConfigurationMap} is equal to another
- * {@link StreamConfigurationMap}.
- *
- * <p>Two vectors are only equal if and only if each of the respective elements is equal.</p>
- *
- * @return {@code true} if the objects were equal, {@code false} otherwise
- */
- @Override
- public boolean equals(final Object obj) {
- if (obj == null) {
- return false;
- }
- if (this == obj) {
- return true;
- }
- if (obj instanceof StreamConfigurationMap) {
- final StreamConfigurationMap other = (StreamConfigurationMap) obj;
- // TODO: do we care about order?
- return Arrays.equals(mConfigurations, other.mConfigurations) &&
- Arrays.equals(mDurations, other.mDurations);
- }
- return false;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int hashCode() {
- // TODO: do we care about order?
- return HashCodeHelpers.hashCode(mConfigurations) ^ HashCodeHelpers.hashCode(mDurations);
- }
-
- // Check that the argument is supported by #getOutputFormats or #getInputFormats
- private int checkArgumentFormatSupported(int format, boolean output) {
- checkArgumentFormat(format);
-
- int[] formats = output ? getOutputFormats() : getInputFormats();
- for (int i = 0; i < formats.length; ++i) {
- if (format == formats[i]) {
- return format;
- }
- }
-
- throw new IllegalArgumentException(String.format(
- "format %x is not supported by this stream configuration map", format));
- }
-
- /**
- * Ensures that the format is either user-defined or implementation defined.
- *
- * <p>Any invalid/undefined formats will raise an exception.</p>
- *
- * @param format image format
- * @return the format
- *
- * @throws IllegalArgumentException if the format was invalid
- */
- static int checkArgumentFormatInternal(int format) {
- if (format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
- return format;
- }
-
- return checkArgumentFormat(format);
- }
-
- /**
- * Ensures that the format is user-defined in either ImageFormat or PixelFormat.
- *
- * <p>Any invalid/undefined formats will raise an exception, including implementation-defined.
- * </p>
- *
- * <p>Note that {@code @hide} and deprecated formats will not pass this check.</p>
- *
- * @param format image format
- * @return the format
- *
- * @throws IllegalArgumentException if the format was not user-defined
- */
- static int checkArgumentFormat(int format) {
- if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) {
- throw new IllegalArgumentException(String.format(
- "format %x was not defined in either ImageFormat or PixelFormat", format));
- }
-
- return format;
- }
-
- private static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
-
- private final StreamConfiguration[] mConfigurations;
- private final StreamConfigurationDuration[] mDurations;
-
-}
diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
new file mode 100644
index 0000000..2647959
--- /dev/null
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.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 android.hardware.camera2;
+
+import android.hardware.camera2.impl.CameraMetadataNative;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * <p>The total assembled results of a single image capture from the image sensor.</p>
+ *
+ * <p>Contains the final configuration for the capture hardware (sensor, lens,
+ * flash), the processing pipeline, the control algorithms, and the output
+ * buffers.</p>
+ *
+ * <p>A {@code TotalCaptureResult} is produced by a {@link CameraDevice} after processing a
+ * {@link CaptureRequest}. All properties listed for capture requests can also
+ * be queried on the capture result, to determine the final values used for
+ * capture. The result also includes additional metadata about the state of the
+ * camera device during the capture.</p>
+ *
+ * <p>All properties returned by {@link CameraCharacteristics#getAvailableCaptureResultKeys()}
+ * are available (that is {@link CaptureResult#get} will return non-{@code null}, if and only if
+ * that key that was enabled by the request. A few keys such as
+ * {@link CaptureResult#STATISTICS_FACES} are disabled by default unless enabled with a switch (such
+ * as {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE}). Refer to each key documentation on
+ * a case-by-case basis.</p>
+ *
+ * <p>{@link TotalCaptureResult} objects are immutable.</p>
+ *
+ * @see CameraDevice.CaptureListener#onCaptureCompleted
+ */
+public final class TotalCaptureResult extends CaptureResult {
+
+ /**
+ * Takes ownership of the passed-in properties object
+ * @hide
+ */
+ public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent, int sequenceId) {
+ super(results, parent, sequenceId);
+ }
+
+ /**
+ * Creates a request-less result.
+ *
+ * <p><strong>For testing only.</strong></p>
+ * @hide
+ */
+ public TotalCaptureResult(CameraMetadataNative results, int sequenceId) {
+ super(results, sequenceId);
+ }
+
+ /**
+ * Get the read-only list of partial results that compose this total result.
+ *
+ * <p>The list is returned is unmodifiable; attempting to modify it will result in a
+ * {@code UnsupportedOperationException} being thrown.</p>
+ *
+ * <p>The list size will be inclusive between {@code 1} and
+ * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT}, in ascending order
+ * of when {@link CameraDevice.CaptureListener#onCaptureProgressed} was invoked.</p>
+ *
+ * @return unmodifiable list of partial results
+ */
+ public List<CaptureResult> getPartialResults() {
+ // TODO
+ return Collections.unmodifiableList(null);
+ }
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index 988f8f9..9a4c531 100644
--- a/core/java/android/hardware/camera2/impl/CameraDevice.java
+++ b/core/java/android/hardware/camera2/impl/CameraDevice.java
@@ -19,14 +19,17 @@ package android.hardware.camera2.impl;
import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE;
import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
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.TotalCaptureResult;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
+import android.hardware.camera2.utils.LongParcelable;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -72,6 +75,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
private final SparseArray<Surface> mConfiguredOutputs = new SparseArray<Surface>();
private final String mCameraId;
+ private final CameraCharacteristics mCharacteristics;
/**
* A list tracking request and its expected last frame.
@@ -150,13 +154,15 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
};
- public CameraDevice(String cameraId, StateListener listener, Handler handler) {
+ public CameraDevice(String cameraId, StateListener listener, Handler handler,
+ CameraCharacteristics characteristics) {
if (cameraId == null || listener == null || handler == null) {
throw new IllegalArgumentException("Null argument given");
}
mCameraId = cameraId;
mDeviceListener = listener;
mDeviceHandler = handler;
+ mCharacteristics = characteristics;
final int MAX_TAG_LEN = 23;
String tag = String.format("CameraDevice-JV-%s", mCameraId);
@@ -217,7 +223,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
try {
waitUntilIdle();
- // TODO: mRemoteDevice.beginConfigure
+ mRemoteDevice.beginConfigure();
// Delete all streams first (to free up HW resources)
for (Integer streamId : deleteList) {
mRemoteDevice.deleteStream(streamId);
@@ -232,7 +238,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
mConfiguredOutputs.put(streamId, s);
}
- // TODO: mRemoteDevice.endConfigure
+ mRemoteDevice.endConfigure();
} catch (CameraRuntimeException e) {
if (e.getReason() == CAMERA_IN_USE) {
throw new IllegalStateException("The camera is currently busy." +
@@ -254,6 +260,13 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
@Override
+ public void createCaptureSession(List<Surface> outputs,
+ CameraCaptureSession.StateListener listener, Handler handler)
+ throws CameraAccessException {
+ // TODO
+ }
+
+ @Override
public CaptureRequest.Builder createCaptureRequest(int templateType)
throws CameraAccessException {
synchronized (mLock) {
@@ -352,7 +365,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
holder.getListener().onCaptureSequenceCompleted(
CameraDevice.this,
requestId,
- (int)lastFrameNumber);
+ lastFrameNumber);
}
}
};
@@ -710,7 +723,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
holder.getListener().onCaptureSequenceCompleted(
CameraDevice.this,
requestId,
- (int)lastFrameNumber);
+ lastFrameNumber);
}
}
};
@@ -843,11 +856,18 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
@Override
public void onResultReceived(CameraMetadataNative result,
CaptureResultExtras resultExtras) throws RemoteException {
+
int requestId = resultExtras.getRequestId();
if (DEBUG) {
Log.v(TAG, "Received result frame " + resultExtras.getFrameNumber() + " for id "
+ requestId);
}
+
+
+ // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
+ result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
+ getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
+
final CaptureListenerHolder holder;
synchronized (mLock) {
holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
@@ -881,12 +901,15 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
- final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId);
+
Runnable resultDispatch = null;
// Either send a partial result or the final capture completed result
if (quirkIsPartialResult) {
+ final CaptureResult resultAsCapture =
+ new CaptureResult(result, request, requestId);
+
// Partial result
resultDispatch = new Runnable() {
@Override
@@ -900,6 +923,9 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
};
} else {
+ final TotalCaptureResult resultAsCapture =
+ new TotalCaptureResult(result, request, requestId);
+
// Final capture result
resultDispatch = new Runnable() {
@Override
@@ -951,4 +977,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
return (mRemoteDevice == null);
}
}
+
+ private CameraCharacteristics getCharacteristics() {
+ return mCharacteristics;
+ }
}
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index c5e5753..83aee5d 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -20,15 +20,47 @@ import android.graphics.ImageFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.Face;
-import android.hardware.camera2.Rational;
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.marshal.MarshalRegistry;
+import android.hardware.camera2.marshal.impl.MarshalQueryableArray;
+import android.hardware.camera2.marshal.impl.MarshalQueryableBoolean;
+import android.hardware.camera2.marshal.impl.MarshalQueryableColorSpaceTransform;
+import android.hardware.camera2.marshal.impl.MarshalQueryableEnum;
+import android.hardware.camera2.marshal.impl.MarshalQueryableMeteringRectangle;
+import android.hardware.camera2.marshal.impl.MarshalQueryableNativeByteToInteger;
+import android.hardware.camera2.marshal.impl.MarshalQueryablePair;
+import android.hardware.camera2.marshal.impl.MarshalQueryableParcelable;
+import android.hardware.camera2.marshal.impl.MarshalQueryablePrimitive;
+import android.hardware.camera2.marshal.impl.MarshalQueryableRange;
+import android.hardware.camera2.marshal.impl.MarshalQueryableRect;
+import android.hardware.camera2.marshal.impl.MarshalQueryableReprocessFormatsMap;
+import android.hardware.camera2.marshal.impl.MarshalQueryableRggbChannelVector;
+import android.hardware.camera2.marshal.impl.MarshalQueryableSize;
+import android.hardware.camera2.marshal.impl.MarshalQueryableSizeF;
+import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfiguration;
+import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfigurationDuration;
+import android.hardware.camera2.marshal.impl.MarshalQueryableString;
+import android.hardware.camera2.params.Face;
+import android.hardware.camera2.params.LensShadingMap;
+import android.hardware.camera2.params.StreamConfiguration;
+import android.hardware.camera2.params.StreamConfigurationDuration;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.params.TonemapCurve;
+import android.hardware.camera2.utils.TypeReference;
+import android.location.Location;
+import android.location.LocationManager;
import android.os.Parcelable;
import android.os.Parcel;
import android.util.Log;
+import android.util.Pair;
+import android.util.Size;
-import java.lang.reflect.Array;
+import com.android.internal.util.Preconditions;
+
+import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
@@ -38,12 +70,183 @@ import java.util.HashMap;
* Implementation of camera metadata marshal/unmarshal across Binder to
* the camera service
*/
-public class CameraMetadataNative extends CameraMetadata implements Parcelable {
+public class CameraMetadataNative implements Parcelable {
+
+ public static class Key<T> {
+ private boolean mHasTag;
+ private int mTag;
+ private final Class<T> mType;
+ private final TypeReference<T> mTypeReference;
+ private final String mName;
+
+ /**
+ * Visible for testing only.
+ *
+ * <p>Use the CameraCharacteristics.Key, CaptureResult.Key, or CaptureRequest.Key
+ * for application code or vendor-extended keys.</p>
+ */
+ public Key(String name, Class<T> type) {
+ if (name == null) {
+ throw new NullPointerException("Key needs a valid name");
+ } else if (type == null) {
+ throw new NullPointerException("Type needs to be non-null");
+ }
+ mName = name;
+ mType = type;
+ mTypeReference = TypeReference.createSpecializedTypeReference(type);
+ }
+
+ /**
+ * Visible for testing only.
+ *
+ * <p>Use the CameraCharacteristics.Key, CaptureResult.Key, or CaptureRequest.Key
+ * for application code or vendor-extended keys.</p>
+ */
+ @SuppressWarnings("unchecked")
+ public Key(String name, TypeReference<T> typeReference) {
+ if (name == null) {
+ throw new NullPointerException("Key needs a valid name");
+ } else if (typeReference == null) {
+ throw new NullPointerException("TypeReference needs to be non-null");
+ }
+ mName = name;
+ mType = (Class<T>)typeReference.getRawType();
+ mTypeReference = typeReference;
+ }
+
+ /**
+ * Return a camelCase, period separated name formatted like:
+ * {@code "root.section[.subsections].name"}.
+ *
+ * <p>Built-in keys exposed by the Android SDK are always prefixed with {@code "android."};
+ * keys that are device/platform-specific are prefixed with {@code "com."}.</p>
+ *
+ * <p>For example, {@code CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP} would
+ * have a name of {@code "android.scaler.streamConfigurationMap"}; whereas a device
+ * specific key might look like {@code "com.google.nexus.data.private"}.</p>
+ *
+ * @return String representation of the key name
+ */
+ public final String getName() {
+ return mName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final int hashCode() {
+ return mName.hashCode() ^ mTypeReference.hashCode();
+ }
+
+ /**
+ * Compare this key against other native keys, request keys, result keys, and
+ * characteristics keys.
+ *
+ * <p>Two keys are considered equal if their name and type reference are equal.</p>
+ *
+ * <p>Note that the equality against non-native keys is one-way. A native key may be equal
+ * to a result key; but that same result key will not be equal to a native key.</p>
+ */
+ @SuppressWarnings("rawtypes")
+ @Override
+ public final boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ Key<?> lhs;
+
+ if (o instanceof CaptureResult.Key) {
+ lhs = ((CaptureResult.Key)o).getNativeKey();
+ } else if (o instanceof CaptureRequest.Key) {
+ lhs = ((CaptureRequest.Key)o).getNativeKey();
+ } else if (o instanceof CameraCharacteristics.Key) {
+ lhs = ((CameraCharacteristics.Key)o).getNativeKey();
+ } else if ((o instanceof Key)) {
+ lhs = (Key<?>)o;
+ } else {
+ return false;
+ }
+
+ return mName.equals(lhs.mName) && mTypeReference.equals(lhs.mTypeReference);
+ }
+
+ /**
+ * <p>
+ * Get the tag corresponding to this key. This enables insertion into the
+ * native metadata.
+ * </p>
+ *
+ * <p>This value is looked up the first time, and cached subsequently.</p>
+ *
+ * @return The tag numeric value corresponding to the string
+ */
+ public final int getTag() {
+ if (!mHasTag) {
+ mTag = CameraMetadataNative.getTag(mName);
+ mHasTag = true;
+ }
+ return mTag;
+ }
+
+ /**
+ * Get the raw class backing the type {@code T} for this key.
+ *
+ * <p>The distinction is only important if {@code T} is a generic, e.g.
+ * {@code Range<Integer>} since the nested type will be erased.</p>
+ */
+ public final Class<T> getType() {
+ // TODO: remove this; other places should use #getTypeReference() instead
+ return mType;
+ }
+
+ /**
+ * Get the type reference backing the type {@code T} for this key.
+ *
+ * <p>The distinction is only important if {@code T} is a generic, e.g.
+ * {@code Range<Integer>} since the nested type will be retained.</p>
+ */
+ public final TypeReference<T> getTypeReference() {
+ return mTypeReference;
+ }
+ }
private static final String TAG = "CameraMetadataJV";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
// this should be in sync with HAL_PIXEL_FORMAT_BLOB defined in graphics.h
- private static final int NATIVE_JPEG_FORMAT = 0x21;
+ public static final int NATIVE_JPEG_FORMAT = 0x21;
+
+ private static final String CELLID_PROCESS = "CELLID";
+ private static final String GPS_PROCESS = "GPS";
+
+ private static String translateLocationProviderToProcess(final String provider) {
+ if (provider == null) {
+ return null;
+ }
+ switch(provider) {
+ case LocationManager.GPS_PROVIDER:
+ return GPS_PROCESS;
+ case LocationManager.NETWORK_PROVIDER:
+ return CELLID_PROCESS;
+ default:
+ return null;
+ }
+ }
+
+ private static String translateProcessToLocationProvider(final String process) {
+ if (process == null) {
+ return null;
+ }
+ switch(process) {
+ case GPS_PROCESS:
+ return LocationManager.GPS_PROVIDER;
+ case CELLID_PROCESS:
+ return LocationManager.NETWORK_PROVIDER;
+ default:
+ return null;
+ }
+ }
public CameraMetadataNative() {
super();
@@ -64,6 +267,20 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
}
}
+ /**
+ * Move the contents from {@code other} into a new camera metadata instance.</p>
+ *
+ * <p>After this call, {@code other} will become empty.</p>
+ *
+ * @param other the previous metadata instance which will get pilfered
+ * @return a new metadata instance with the values from {@code other} moved into it
+ */
+ public static CameraMetadataNative move(CameraMetadataNative other) {
+ CameraMetadataNative newObject = new CameraMetadataNative();
+ newObject.swap(other);
+ return newObject;
+ }
+
public static final Parcelable.Creator<CameraMetadataNative> CREATOR =
new Parcelable.Creator<CameraMetadataNative>() {
@Override
@@ -89,12 +306,39 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
nativeWriteToParcel(dest);
}
- @SuppressWarnings("unchecked")
- @Override
+ /**
+ * @hide
+ */
+ public <T> T get(CameraCharacteristics.Key<T> key) {
+ return get(key.getNativeKey());
+ }
+
+ /**
+ * @hide
+ */
+ public <T> T get(CaptureResult.Key<T> key) {
+ return get(key.getNativeKey());
+ }
+
+ /**
+ * @hide
+ */
+ public <T> T get(CaptureRequest.Key<T> key) {
+ return get(key.getNativeKey());
+ }
+
+ /**
+ * Look-up a metadata field value by its key.
+ *
+ * @param key a non-{@code null} key instance
+ * @return the field corresponding to the {@code key}, or {@code null} if no value was set
+ */
public <T> T get(Key<T> key) {
- T value = getOverride(key);
- if (value != null) {
- return value;
+ Preconditions.checkNotNull(key, "key must not be null");
+
+ Pair<T, Boolean> override = getOverride(key);
+ if (override.second) {
+ return override.first;
}
return getBase(key);
@@ -133,6 +377,18 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
setBase(key, value);
}
+ public <T> void set(CaptureRequest.Key<T> key, T value) {
+ set(key.getNativeKey(), value);
+ }
+
+ public <T> void set(CaptureResult.Key<T> key, T value) {
+ set(key.getNativeKey(), value);
+ }
+
+ public <T> void set(CameraCharacteristics.Key<T> key, T value) {
+ set(key.getNativeKey(), value);
+ }
+
// Keep up-to-date with camera_metadata.h
/**
* @hide
@@ -169,273 +425,16 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
mMetadataPtr = 0; // set it to 0 again to prevent eclipse from making this field final
}
- private static int getTypeSize(int nativeType) {
- switch(nativeType) {
- case TYPE_BYTE:
- return 1;
- case TYPE_INT32:
- case TYPE_FLOAT:
- return 4;
- case TYPE_INT64:
- case TYPE_DOUBLE:
- case TYPE_RATIONAL:
- return 8;
- }
-
- throw new UnsupportedOperationException("Unknown type, can't get size "
- + nativeType);
- }
-
- private static Class<?> getExpectedType(int nativeType) {
- switch(nativeType) {
- case TYPE_BYTE:
- return Byte.TYPE;
- case TYPE_INT32:
- return Integer.TYPE;
- case TYPE_FLOAT:
- return Float.TYPE;
- case TYPE_INT64:
- return Long.TYPE;
- case TYPE_DOUBLE:
- return Double.TYPE;
- case TYPE_RATIONAL:
- return Rational.class;
- }
-
- throw new UnsupportedOperationException("Unknown type, can't map to Java type "
- + nativeType);
- }
-
- @SuppressWarnings("unchecked")
- private static <T> int packSingleNative(T value, ByteBuffer buffer, Class<T> type,
- int nativeType, boolean sizeOnly) {
-
- if (!sizeOnly) {
- /**
- * Rewrite types when the native type doesn't match the managed type
- * - Boolean -> Byte
- * - Integer -> Byte
- */
-
- if (nativeType == TYPE_BYTE && type == Boolean.TYPE) {
- // Since a boolean can't be cast to byte, and we don't want to use putBoolean
- boolean asBool = (Boolean) value;
- byte asByte = (byte) (asBool ? 1 : 0);
- value = (T) (Byte) asByte;
- } else if (nativeType == TYPE_BYTE && type == Integer.TYPE) {
- int asInt = (Integer) value;
- byte asByte = (byte) asInt;
- value = (T) (Byte) asByte;
- } else if (type != getExpectedType(nativeType)) {
- throw new UnsupportedOperationException("Tried to pack a type of " + type +
- " but we expected the type to be " + getExpectedType(nativeType));
- }
-
- if (nativeType == TYPE_BYTE) {
- buffer.put((Byte) value);
- } else if (nativeType == TYPE_INT32) {
- buffer.putInt((Integer) value);
- } else if (nativeType == TYPE_FLOAT) {
- buffer.putFloat((Float) value);
- } else if (nativeType == TYPE_INT64) {
- buffer.putLong((Long) value);
- } else if (nativeType == TYPE_DOUBLE) {
- buffer.putDouble((Double) value);
- } else if (nativeType == TYPE_RATIONAL) {
- Rational r = (Rational) value;
- buffer.putInt(r.getNumerator());
- buffer.putInt(r.getDenominator());
- }
-
- }
-
- return getTypeSize(nativeType);
+ private <T> T getBase(CameraCharacteristics.Key<T> key) {
+ return getBase(key.getNativeKey());
}
- @SuppressWarnings({"unchecked", "rawtypes"})
- private static <T> int packSingle(T value, ByteBuffer buffer, Class<T> type, int nativeType,
- boolean sizeOnly) {
-
- int size = 0;
-
- if (type.isPrimitive() || type == Rational.class) {
- size = packSingleNative(value, buffer, type, nativeType, sizeOnly);
- } else if (type.isEnum()) {
- size = packEnum((Enum)value, buffer, (Class<Enum>)type, nativeType, sizeOnly);
- } else if (type.isArray()) {
- size = packArray(value, buffer, type, nativeType, sizeOnly);
- } else {
- size = packClass(value, buffer, type, nativeType, sizeOnly);
- }
-
- return size;
- }
-
- private static <T extends Enum<T>> int packEnum(T value, ByteBuffer buffer, Class<T> type,
- int nativeType, boolean sizeOnly) {
-
- // TODO: add support for enums with their own values.
- return packSingleNative(getEnumValue(value), buffer, Integer.TYPE, nativeType, sizeOnly);
- }
-
- @SuppressWarnings("unchecked")
- private static <T> int packClass(T value, ByteBuffer buffer, Class<T> type, int nativeType,
- boolean sizeOnly) {
-
- MetadataMarshalClass<T> marshaler = getMarshaler(type, nativeType);
- if (marshaler == null) {
- throw new IllegalArgumentException(String.format("Unknown Key type: %s", type));
- }
-
- return marshaler.marshal(value, buffer, nativeType, sizeOnly);
- }
-
- private static <T> int packArray(T value, ByteBuffer buffer, Class<T> type, int nativeType,
- boolean sizeOnly) {
-
- int size = 0;
- int arrayLength = Array.getLength(value);
-
- @SuppressWarnings("unchecked")
- Class<Object> componentType = (Class<Object>)type.getComponentType();
-
- for (int i = 0; i < arrayLength; ++i) {
- size += packSingle(Array.get(value, i), buffer, componentType, nativeType, sizeOnly);
- }
-
- return size;
+ private <T> T getBase(CaptureResult.Key<T> key) {
+ return getBase(key.getNativeKey());
}
- @SuppressWarnings("unchecked")
- private static <T> T unpackSingleNative(ByteBuffer buffer, Class<T> type, int nativeType) {
-
- T val;
-
- if (nativeType == TYPE_BYTE) {
- val = (T) (Byte) buffer.get();
- } else if (nativeType == TYPE_INT32) {
- val = (T) (Integer) buffer.getInt();
- } else if (nativeType == TYPE_FLOAT) {
- val = (T) (Float) buffer.getFloat();
- } else if (nativeType == TYPE_INT64) {
- val = (T) (Long) buffer.getLong();
- } else if (nativeType == TYPE_DOUBLE) {
- val = (T) (Double) buffer.getDouble();
- } else if (nativeType == TYPE_RATIONAL) {
- val = (T) new Rational(buffer.getInt(), buffer.getInt());
- } else {
- throw new UnsupportedOperationException("Unknown type, can't unpack a native type "
- + nativeType);
- }
-
- /**
- * Rewrite types when the native type doesn't match the managed type
- * - Byte -> Boolean
- * - Byte -> Integer
- */
-
- if (nativeType == TYPE_BYTE && type == Boolean.TYPE) {
- // Since a boolean can't be cast to byte, and we don't want to use getBoolean
- byte asByte = (Byte) val;
- boolean asBool = asByte != 0;
- val = (T) (Boolean) asBool;
- } else if (nativeType == TYPE_BYTE && type == Integer.TYPE) {
- byte asByte = (Byte) val;
- int asInt = asByte;
- val = (T) (Integer) asInt;
- } else if (type != getExpectedType(nativeType)) {
- throw new UnsupportedOperationException("Tried to unpack a type of " + type +
- " but we expected the type to be " + getExpectedType(nativeType));
- }
-
- return val;
- }
-
- @SuppressWarnings({"unchecked", "rawtypes"})
- private static <T> T unpackSingle(ByteBuffer buffer, Class<T> type, int nativeType) {
-
- if (type.isPrimitive() || type == Rational.class) {
- return unpackSingleNative(buffer, type, nativeType);
- }
-
- if (type.isEnum()) {
- return (T) unpackEnum(buffer, (Class<Enum>)type, nativeType);
- }
-
- if (type.isArray()) {
- return unpackArray(buffer, type, nativeType);
- }
-
- T instance = unpackClass(buffer, type, nativeType);
-
- return instance;
- }
-
- private static <T extends Enum<T>> T unpackEnum(ByteBuffer buffer, Class<T> type,
- int nativeType) {
- int ordinal = unpackSingleNative(buffer, Integer.TYPE, nativeType);
- return getEnumFromValue(type, ordinal);
- }
-
- private static <T> T unpackClass(ByteBuffer buffer, Class<T> type, int nativeType) {
-
- MetadataMarshalClass<T> marshaler = getMarshaler(type, nativeType);
- if (marshaler == null) {
- throw new IllegalArgumentException("Unknown class type: " + type);
- }
-
- return marshaler.unmarshal(buffer, nativeType);
- }
-
- @SuppressWarnings("unchecked")
- private static <T> T unpackArray(ByteBuffer buffer, Class<T> type, int nativeType) {
-
- Class<?> componentType = type.getComponentType();
- Object array;
-
- int elementSize = getTypeSize(nativeType);
-
- MetadataMarshalClass<?> marshaler = getMarshaler(componentType, nativeType);
- if (marshaler != null) {
- elementSize = marshaler.getNativeSize(nativeType);
- }
-
- if (elementSize != MetadataMarshalClass.NATIVE_SIZE_DYNAMIC) {
- int remaining = buffer.remaining();
- int arraySize = remaining / elementSize;
-
- if (VERBOSE) {
- Log.v(TAG,
- String.format(
- "Attempting to unpack array (count = %d, element size = %d, bytes " +
- "remaining = %d) for type %s",
- arraySize, elementSize, remaining, type));
- }
-
- array = Array.newInstance(componentType, arraySize);
- for (int i = 0; i < arraySize; ++i) {
- Object elem = unpackSingle(buffer, componentType, nativeType);
- Array.set(array, i, elem);
- }
- } else {
- // Dynamic size, use an array list.
- ArrayList<Object> arrayList = new ArrayList<Object>();
-
- int primitiveSize = getTypeSize(nativeType);
- while (buffer.remaining() >= primitiveSize) {
- Object elem = unpackSingle(buffer, componentType, nativeType);
- arrayList.add(elem);
- }
-
- array = arrayList.toArray((T[]) Array.newInstance(componentType, 0));
- }
-
- if (buffer.remaining() != 0) {
- Log.e(TAG, "Trailing bytes (" + buffer.remaining() + ") left over after unpacking "
- + type);
- }
-
- return (T) array;
+ private <T> T getBase(CaptureRequest.Key<T> key) {
+ return getBase(key.getNativeKey());
}
private <T> T getBase(Key<T> key) {
@@ -445,30 +444,48 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
return null;
}
- int nativeType = getNativeType(tag);
-
+ Marshaler<T> marshaler = getMarshalerForKey(key);
ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder());
- return unpackSingle(buffer, key.getType(), nativeType);
+ return marshaler.unmarshal(buffer);
}
-
// Need overwrite some metadata that has different definitions between native
// and managed sides.
@SuppressWarnings("unchecked")
- private <T> T getOverride(Key<T> key) {
+ private <T> Pair<T, Boolean> getOverride(Key<T> key) {
+ T value = null;
+ boolean override = true;
+
if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)) {
- return (T) getAvailableFormats();
+ value = (T) getAvailableFormats();
} else if (key.equals(CaptureResult.STATISTICS_FACES)) {
- return (T) getFaces();
+ value = (T) getFaces();
} else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) {
- 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();
+ value = (T) getFaceRectangles();
+ } else if (key.equals(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)) {
+ value = (T) getStreamConfigurationMap();
+ } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AE)) {
+ value = (T) getMaxRegions(key);
+ } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB)) {
+ value = (T) getMaxRegions(key);
+ } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AF)) {
+ value = (T) getMaxRegions(key);
+ } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW)) {
+ value = (T) getMaxNumOutputs(key);
+ } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC)) {
+ value = (T) getMaxNumOutputs(key);
+ } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC_STALLING)) {
+ value = (T) getMaxNumOutputs(key);
+ } else if (key.equals(CaptureRequest.TONEMAP_CURVE)) {
+ value = (T) getTonemapCurve();
+ } else if (key.equals(CaptureResult.JPEG_GPS_LOCATION)) {
+ value = (T) getGpsLocation();
+ } else if (key.equals(CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP)) {
+ value = (T) getLensShadingMap();
+ } else {
+ override = false;
}
- // For other keys, get() falls back to getBase()
- return null;
+ return Pair.create(value, override);
}
private int[] getAvailableFormats() {
@@ -485,50 +502,6 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
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;
@@ -628,23 +601,159 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
return fixedFaceRectangles;
}
+ private LensShadingMap getLensShadingMap() {
+ float[] lsmArray = getBase(CaptureResult.STATISTICS_LENS_SHADING_MAP);
+ if (lsmArray == null) {
+ Log.w(TAG, "getLensShadingMap - Lens shading map was null.");
+ return null;
+ }
+ Size s = get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE);
+ LensShadingMap map = new LensShadingMap(lsmArray, s.getHeight(), s.getWidth());
+ return map;
+ }
+
+ private Location getGpsLocation() {
+ String processingMethod = get(CaptureResult.JPEG_GPS_PROCESSING_METHOD);
+ Location l = new Location(translateProcessToLocationProvider(processingMethod));
+
+ double[] coords = get(CaptureResult.JPEG_GPS_COORDINATES);
+ Long timeStamp = get(CaptureResult.JPEG_GPS_TIMESTAMP);
+
+ if (timeStamp != null) {
+ l.setTime(timeStamp);
+ } else {
+ Log.w(TAG, "getGpsLocation - No timestamp for GPS location.");
+ }
+
+ if (coords != null) {
+ l.setLatitude(coords[0]);
+ l.setLongitude(coords[1]);
+ l.setAltitude(coords[2]);
+ } else {
+ Log.w(TAG, "getGpsLocation - No coordinates for GPS location");
+ }
+
+ return l;
+ }
+
+ private boolean setGpsLocation(Location l) {
+ if (l == null) {
+ return false;
+ }
+
+ double[] coords = { l.getLatitude(), l.getLongitude(), l.getAltitude() };
+ String processMethod = translateLocationProviderToProcess(l.getProvider());
+ long timestamp = l.getTime();
+
+ set(CaptureRequest.JPEG_GPS_TIMESTAMP, timestamp);
+ set(CaptureRequest.JPEG_GPS_COORDINATES, coords);
+
+ if (processMethod == null) {
+ Log.w(TAG, "setGpsLocation - No process method, Location is not from a GPS or NETWORK" +
+ "provider");
+ } else {
+ setBase(CaptureRequest.JPEG_GPS_PROCESSING_METHOD, processMethod);
+ }
+ return true;
+ }
+
+ private StreamConfigurationMap getStreamConfigurationMap() {
+ StreamConfiguration[] configurations = getBase(
+ CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS);
+ StreamConfigurationDuration[] minFrameDurations = getBase(
+ CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS);
+ StreamConfigurationDuration[] stallDurations = getBase(
+ CameraCharacteristics.SCALER_AVAILABLE_STALL_DURATIONS);
+
+ return new StreamConfigurationMap(configurations, minFrameDurations, stallDurations);
+ }
+
+ private <T> Integer getMaxRegions(Key<T> key) {
+ final int AE = 0;
+ final int AWB = 1;
+ final int AF = 2;
+
+ // The order of the elements is: (AE, AWB, AF)
+ int[] maxRegions = getBase(CameraCharacteristics.CONTROL_MAX_REGIONS);
+
+ if (maxRegions == null) {
+ return null;
+ }
+
+ if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AE)) {
+ return maxRegions[AE];
+ } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB)) {
+ return maxRegions[AWB];
+ } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AF)) {
+ return maxRegions[AF];
+ } else {
+ throw new AssertionError("Invalid key " + key);
+ }
+ }
+
+ private <T> Integer getMaxNumOutputs(Key<T> key) {
+ final int RAW = 0;
+ final int PROC = 1;
+ final int PROC_STALLING = 2;
+
+ // The order of the elements is: (raw, proc+nonstalling, proc+stalling)
+ int[] maxNumOutputs = getBase(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_STREAMS);
+
+ if (maxNumOutputs == null) {
+ return null;
+ }
+
+ if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW)) {
+ return maxNumOutputs[RAW];
+ } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC)) {
+ return maxNumOutputs[PROC];
+ } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC_STALLING)) {
+ return maxNumOutputs[PROC_STALLING];
+ } else {
+ throw new AssertionError("Invalid key " + key);
+ }
+ }
+
+ private <T> TonemapCurve getTonemapCurve() {
+ float[] red = getBase(CaptureRequest.TONEMAP_CURVE_RED);
+ float[] green = getBase(CaptureRequest.TONEMAP_CURVE_GREEN);
+ float[] blue = getBase(CaptureRequest.TONEMAP_CURVE_BLUE);
+ if (red == null || green == null || blue == null) {
+ return null;
+ }
+ TonemapCurve tc = new TonemapCurve(red, green, blue);
+ return tc;
+ }
+
+ private <T> void setBase(CameraCharacteristics.Key<T> key, T value) {
+ setBase(key.getNativeKey(), value);
+ }
+
+ private <T> void setBase(CaptureResult.Key<T> key, T value) {
+ setBase(key.getNativeKey(), value);
+ }
+
+ private <T> void setBase(CaptureRequest.Key<T> key, T value) {
+ setBase(key.getNativeKey(), value);
+ }
+
private <T> void setBase(Key<T> key, T value) {
int tag = key.getTag();
if (value == null) {
- writeValues(tag, null);
+ // Erase the entry
+ writeValues(tag, /*src*/null);
return;
- }
-
- int nativeType = getNativeType(tag);
+ } // else update the entry to a new value
- int size = packSingle(value, null, key.getType(), nativeType, /* sizeOnly */true);
+ Marshaler<T> marshaler = getMarshalerForKey(key);
+ int size = marshaler.calculateMarshalSize(value);
// TODO: Optimization. Cache the byte[] and reuse if the size is big enough.
byte[] values = new byte[size];
ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder());
- packSingle(value, buffer, key.getType(), nativeType, /*sizeOnly*/false);
+ marshaler.marshal(value, buffer);
writeValues(tag, values);
}
@@ -655,56 +764,15 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
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);
+ } else if (key.equals(CaptureRequest.TONEMAP_CURVE)) {
+ return setTonemapCurve((TonemapCurve) value);
+ } else if (key.equals(CaptureResult.JPEG_GPS_LOCATION)) {
+ return setGpsLocation((Location) 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) {
@@ -754,6 +822,24 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
return true;
}
+ private <T> boolean setTonemapCurve(TonemapCurve tc) {
+ if (tc == null) {
+ return false;
+ }
+
+ float[][] curve = new float[3][];
+ for (int i = TonemapCurve.CHANNEL_RED; i <= TonemapCurve.CHANNEL_BLUE; i++) {
+ int pointCount = tc.getPointCount(i);
+ curve[i] = new float[pointCount * TonemapCurve.POINT_SIZE];
+ tc.copyColorCurve(i, curve[i], 0);
+ }
+ setBase(CaptureRequest.TONEMAP_CURVE_RED, curve[0]);
+ setBase(CaptureRequest.TONEMAP_CURVE_GREEN, curve[1]);
+ setBase(CaptureRequest.TONEMAP_CURVE_BLUE, curve[2]);
+
+ return true;
+ }
+
private long mMetadataPtr; // native CameraMetadata*
private native long nativeAllocate();
@@ -770,6 +856,7 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
private native synchronized byte[] nativeReadValues(int tag);
private native synchronized void nativeWriteValues(int tag, byte[] src);
+ private native synchronized void nativeDump() throws IOException; // dump to ALOGD
private static native int nativeGetTagFromKey(String keyName)
throws IllegalArgumentException;
@@ -861,134 +948,89 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
return nativeReadValues(tag);
}
- @Override
- protected void finalize() throws Throwable {
- try {
- close();
- } finally {
- super.finalize();
- }
- }
-
- private static final HashMap<Class<? extends Enum>, int[]> sEnumValues =
- new HashMap<Class<? extends Enum>, int[]>();
/**
- * Register a non-sequential set of values to be used with the pack/unpack functions.
- * This enables get/set to correctly marshal the enum into a value that is C-compatible.
+ * Dumps the native metadata contents to logcat.
*
- * @param enumType The class for an enum
- * @param values A list of values mapping to the ordinals of the enum
+ * <p>Visibility for testing/debugging only. The results will not
+ * include any synthesized keys, as they are invisible to the native layer.</p>
*
* @hide
*/
- public static <T extends Enum<T>> void registerEnumValues(Class<T> enumType, int[] values) {
- if (enumType.getEnumConstants().length != values.length) {
- throw new IllegalArgumentException(
- "Expected values array to be the same size as the enumTypes values "
- + values.length + " for type " + enumType);
- }
- if (VERBOSE) {
- Log.v(TAG, "Registered enum values for type " + enumType + " values");
+ public void dumpToLog() {
+ try {
+ nativeDump();
+ } catch (IOException e) {
+ Log.wtf(TAG, "Dump logging failed", e);
}
-
- sEnumValues.put(enumType, values);
}
- /**
- * Get the numeric value from an enum. This is usually the same as the ordinal value for
- * enums that have fully sequential values, although for C-style enums the range of values
- * may not map 1:1.
- *
- * @param enumValue Enum instance
- * @return Int guaranteed to be ABI-compatible with the C enum equivalent
- */
- private static <T extends Enum<T>> int getEnumValue(T enumValue) {
- int[] values;
- values = sEnumValues.get(enumValue.getClass());
-
- int ordinal = enumValue.ordinal();
- if (values != null) {
- return values[ordinal];
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
}
-
- return ordinal;
}
/**
- * Finds the enum corresponding to it's numeric value. Opposite of {@link #getEnumValue} method.
+ * Get the marshaler compatible with the {@code key} and type {@code T}.
*
- * @param enumType Class of the enum we want to find
- * @param value The numeric value of the enum
- * @return An instance of the enum
+ * @throws UnsupportedOperationException
+ * if the native/managed type combination for {@code key} is not supported
*/
- private static <T extends Enum<T>> T getEnumFromValue(Class<T> enumType, int value) {
- int ordinal;
-
- int[] registeredValues = sEnumValues.get(enumType);
- if (registeredValues != null) {
- ordinal = -1;
-
- for (int i = 0; i < registeredValues.length; ++i) {
- if (registeredValues[i] == value) {
- ordinal = i;
- break;
- }
- }
- } else {
- ordinal = value;
- }
-
- T[] values = enumType.getEnumConstants();
-
- if (ordinal < 0 || ordinal >= values.length) {
- throw new IllegalArgumentException(
- String.format(
- "Argument 'value' (%d) was not a valid enum value for type %s "
- + "(registered? %b)",
- value,
- enumType, (registeredValues != null)));
- }
-
- return values[ordinal];
- }
-
- static HashMap<Class<?>, MetadataMarshalClass<?>> sMarshalerMap = new
- HashMap<Class<?>, MetadataMarshalClass<?>>();
-
- private static <T> void registerMarshaler(MetadataMarshalClass<T> marshaler) {
- sMarshalerMap.put(marshaler.getMarshalingClass(), marshaler);
+ private static <T> Marshaler<T> getMarshalerForKey(Key<T> key) {
+ return MarshalRegistry.getMarshaler(key.getTypeReference(),
+ getNativeType(key.getTag()));
}
- @SuppressWarnings("unchecked")
- private static <T> MetadataMarshalClass<T> getMarshaler(Class<T> type, int nativeType) {
- MetadataMarshalClass<T> marshaler = (MetadataMarshalClass<T>) sMarshalerMap.get(type);
-
- if (marshaler != null && !marshaler.isNativeTypeSupported(nativeType)) {
- throw new UnsupportedOperationException("Unsupported type " + nativeType +
- " to be marshalled to/from a " + type);
- }
-
- return marshaler;
- }
-
- /**
- * We use a class initializer to allow the native code to cache some field offsets
- */
- static {
- nativeClassInit();
-
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private static void registerAllMarshalers() {
if (VERBOSE) {
Log.v(TAG, "Shall register metadata marshalers");
}
- // load built-in marshallers
- registerMarshaler(new MetadataMarshalRect());
- registerMarshaler(new MetadataMarshalSize());
- registerMarshaler(new MetadataMarshalString());
-
+ MarshalQueryable[] queryList = new MarshalQueryable[] {
+ // marshalers for standard types
+ new MarshalQueryablePrimitive(),
+ new MarshalQueryableEnum(),
+ new MarshalQueryableArray(),
+
+ // pseudo standard types, that expand/narrow the native type into a managed type
+ new MarshalQueryableBoolean(),
+ new MarshalQueryableNativeByteToInteger(),
+
+ // marshalers for custom types
+ new MarshalQueryableRect(),
+ new MarshalQueryableSize(),
+ new MarshalQueryableSizeF(),
+ new MarshalQueryableString(),
+ new MarshalQueryableReprocessFormatsMap(),
+ new MarshalQueryableRange(),
+ new MarshalQueryablePair(),
+ new MarshalQueryableMeteringRectangle(),
+ new MarshalQueryableColorSpaceTransform(),
+ new MarshalQueryableStreamConfiguration(),
+ new MarshalQueryableStreamConfigurationDuration(),
+ new MarshalQueryableRggbChannelVector(),
+
+ // generic parcelable marshaler (MUST BE LAST since it has lowest priority)
+ new MarshalQueryableParcelable(),
+ };
+
+ for (MarshalQueryable query : queryList) {
+ MarshalRegistry.registerMarshalQueryable(query);
+ }
if (VERBOSE) {
Log.v(TAG, "Registered metadata marshalers");
}
}
+ static {
+ /*
+ * We use a class initializer to allow the native code to cache some field offsets
+ */
+ nativeClassInit();
+ registerAllMarshalers();
+ }
}
diff --git a/core/java/android/hardware/camera2/CaptureResultExtras.aidl b/core/java/android/hardware/camera2/impl/CaptureResultExtras.aidl
index 6587f02..ebc812a 100644
--- a/core/java/android/hardware/camera2/CaptureResultExtras.aidl
+++ b/core/java/android/hardware/camera2/impl/CaptureResultExtras.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.hardware.camera2;
+package android.hardware.camera2.impl;
/** @hide */
parcelable CaptureResultExtras;
diff --git a/core/java/android/hardware/camera2/CaptureResultExtras.java b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java
index e5c2c1c..7544045 100644
--- a/core/java/android/hardware/camera2/CaptureResultExtras.java
+++ b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.hardware.camera2;
+package android.hardware.camera2.impl;
import android.os.Parcel;
import android.os.Parcelable;
@@ -45,6 +45,15 @@ public class CaptureResultExtras implements Parcelable {
readFromParcel(in);
}
+ public CaptureResultExtras(int requestId, int subsequenceId, int afTriggerId,
+ int precaptureTriggerId, long frameNumber) {
+ this.requestId = requestId;
+ this.subsequenceId = subsequenceId;
+ this.afTriggerId = afTriggerId;
+ this.precaptureTriggerId = precaptureTriggerId;
+ this.frameNumber = frameNumber;
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/hardware/camera2/impl/MetadataMarshalClass.java b/core/java/android/hardware/camera2/impl/MetadataMarshalClass.java
deleted file mode 100644
index 6d224ef..0000000
--- a/core/java/android/hardware/camera2/impl/MetadataMarshalClass.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.hardware.camera2.impl;
-
-import java.nio.ByteBuffer;
-
-public interface MetadataMarshalClass<T> {
-
- /**
- * Marshal the specified object instance (value) into a byte buffer.
- *
- * @param value the value of type T that we wish to write into the byte buffer
- * @param buffer the byte buffer into which the marshalled object will be written
- * @param nativeType the native type, e.g.
- * {@link android.hardware.camera2.impl.CameraMetadataNative#TYPE_BYTE TYPE_BYTE}.
- * Guaranteed to be one for which isNativeTypeSupported returns true.
- * @param sizeOnly if this is true, don't write to the byte buffer. calculate the size only.
- * @return the size that needs to be written to the byte buffer
- */
- int marshal(T value, ByteBuffer buffer, int nativeType, boolean sizeOnly);
-
- /**
- * Unmarshal a new object instance from the byte buffer.
- * @param buffer the byte buffer, from which we will read the object
- * @param nativeType the native type, e.g.
- * {@link android.hardware.camera2.impl.CameraMetadataNative#TYPE_BYTE TYPE_BYTE}.
- * Guaranteed to be one for which isNativeTypeSupported returns true.
- * @return a new instance of type T read from the byte buffer
- */
- T unmarshal(ByteBuffer buffer, int nativeType);
-
- Class<T> getMarshalingClass();
-
- /**
- * Determines whether or not this marshaller supports this native type. Most marshallers
- * will are likely to only support one type.
- *
- * @param nativeType the native type, e.g.
- * {@link android.hardware.camera2.impl.CameraMetadataNative#TYPE_BYTE TYPE_BYTE}
- * @return true if it supports, false otherwise
- */
- boolean isNativeTypeSupported(int nativeType);
-
- public static int NATIVE_SIZE_DYNAMIC = -1;
-
- /**
- * How many bytes T will take up if marshalled to/from nativeType
- * @param nativeType the native type, e.g.
- * {@link android.hardware.camera2.impl.CameraMetadataNative#TYPE_BYTE TYPE_BYTE}
- * @return a size in bytes, or NATIVE_SIZE_DYNAMIC if the size is dynamic
- */
- int getNativeSize(int nativeType);
-}
diff --git a/core/java/android/hardware/camera2/impl/MetadataMarshalRect.java b/core/java/android/hardware/camera2/impl/MetadataMarshalRect.java
deleted file mode 100644
index ab72c4f..0000000
--- a/core/java/android/hardware/camera2/impl/MetadataMarshalRect.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.hardware.camera2.impl;
-
-import android.graphics.Rect;
-
-import java.nio.ByteBuffer;
-
-public class MetadataMarshalRect implements MetadataMarshalClass<Rect> {
- private static final int SIZE = 16;
-
- @Override
- public int marshal(Rect value, ByteBuffer buffer, int nativeType, boolean sizeOnly) {
- if (sizeOnly) {
- return SIZE;
- }
-
- buffer.putInt(value.left);
- buffer.putInt(value.top);
- buffer.putInt(value.width());
- buffer.putInt(value.height());
-
- return SIZE;
- }
-
- @Override
- public Rect unmarshal(ByteBuffer buffer, int nativeType) {
-
- int left = buffer.getInt();
- int top = buffer.getInt();
- int width = buffer.getInt();
- int height = buffer.getInt();
-
- int right = left + width;
- int bottom = top + height;
-
- return new Rect(left, top, right, bottom);
- }
-
- @Override
- public Class<Rect> getMarshalingClass() {
- return Rect.class;
- }
-
- @Override
- public boolean isNativeTypeSupported(int nativeType) {
- return nativeType == CameraMetadataNative.TYPE_INT32;
- }
-
- @Override
- public int getNativeSize(int nativeType) {
- return SIZE;
- }
-}
diff --git a/core/java/android/hardware/camera2/impl/MetadataMarshalSize.java b/core/java/android/hardware/camera2/impl/MetadataMarshalSize.java
deleted file mode 100644
index e8143e0..0000000
--- a/core/java/android/hardware/camera2/impl/MetadataMarshalSize.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.hardware.camera2.impl;
-
-import android.hardware.camera2.Size;
-
-import java.nio.ByteBuffer;
-
-public class MetadataMarshalSize implements MetadataMarshalClass<Size> {
-
- private static final int SIZE = 8;
-
- @Override
- public int marshal(Size value, ByteBuffer buffer, int nativeType, boolean sizeOnly) {
- if (sizeOnly) {
- return SIZE;
- }
-
- buffer.putInt(value.getWidth());
- buffer.putInt(value.getHeight());
-
- return SIZE;
- }
-
- @Override
- public Size unmarshal(ByteBuffer buffer, int nativeType) {
- int width = buffer.getInt();
- int height = buffer.getInt();
-
- return new Size(width, height);
- }
-
- @Override
- public Class<Size> getMarshalingClass() {
- return Size.class;
- }
-
- @Override
- public boolean isNativeTypeSupported(int nativeType) {
- return nativeType == CameraMetadataNative.TYPE_INT32;
- }
-
- @Override
- public int getNativeSize(int nativeType) {
- return SIZE;
- }
-}
diff --git a/core/java/android/hardware/camera2/impl/MetadataMarshalString.java b/core/java/android/hardware/camera2/impl/MetadataMarshalString.java
deleted file mode 100644
index b61b8d3..0000000
--- a/core/java/android/hardware/camera2/impl/MetadataMarshalString.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.hardware.camera2.impl;
-
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-
-public class MetadataMarshalString implements MetadataMarshalClass<String> {
-
- private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
-
- @Override
- public int marshal(String value, ByteBuffer buffer, int nativeType, boolean sizeOnly) {
- byte[] arr = value.getBytes(UTF8_CHARSET);
-
- if (!sizeOnly) {
- buffer.put(arr);
- buffer.put((byte)0); // metadata strings are NULL-terminated
- }
-
- return arr.length + 1;
- }
-
- @Override
- public String unmarshal(ByteBuffer buffer, int nativeType) {
-
- buffer.mark(); // save the current position
-
- boolean foundNull = false;
- int stringLength = 0;
- while (buffer.hasRemaining()) {
- if (buffer.get() == (byte)0) {
- foundNull = true;
- break;
- }
-
- stringLength++;
- }
- if (!foundNull) {
- throw new IllegalArgumentException("Strings must be null-terminated");
- }
-
- buffer.reset(); // go back to the previously marked position
-
- byte[] strBytes = new byte[stringLength + 1];
- buffer.get(strBytes, /*dstOffset*/0, stringLength + 1); // including null character
-
- // not including null character
- return new String(strBytes, /*offset*/0, stringLength, UTF8_CHARSET);
- }
-
- @Override
- public Class<String> getMarshalingClass() {
- return String.class;
- }
-
- @Override
- public boolean isNativeTypeSupported(int nativeType) {
- return nativeType == CameraMetadataNative.TYPE_BYTE;
- }
-
- @Override
- public int getNativeSize(int nativeType) {
- return NATIVE_SIZE_DYNAMIC;
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/BurstHolder.java b/core/java/android/hardware/camera2/legacy/BurstHolder.java
new file mode 100644
index 0000000..e35eb50
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/BurstHolder.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.hardware.camera2.legacy;
+
+import android.hardware.camera2.CaptureRequest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Immutable container for a burst of capture results.
+ */
+public class BurstHolder {
+
+ private final ArrayList<CaptureRequest> mRequests;
+ private final boolean mRepeating;
+ private final int mRequestId;
+
+ /**
+ * Immutable container for a burst of capture results.
+ *
+ * @param requestId id of the burst request.
+ * @param repeating true if this burst is repeating.
+ * @param requests a {@link java.util.List} of {@link CaptureRequest}s in this burst.
+ */
+ public BurstHolder(int requestId, boolean repeating, List<CaptureRequest> requests) {
+ mRequests = new ArrayList<CaptureRequest>(requests);
+ mRepeating = repeating;
+ mRequestId = requestId;
+ }
+
+ /**
+ * Get the id of this request.
+ */
+ public int getRequestId() {
+ return mRequestId;
+ }
+
+ /**
+ * Return true if this repeating.
+ */
+ public boolean isRepeating() {
+ return mRepeating;
+ }
+
+ /**
+ * Return the number of requests in this burst sequence.
+ */
+ public int getNumberOfRequests() {
+ return mRequests.size();
+ }
+
+ /**
+ * Create a list of {@link RequestHolder} objects encapsulating the requests in this burst.
+ *
+ * @param frameNumber the starting framenumber for this burst.
+ * @return the list of {@link RequestHolder} objects.
+ */
+ public List<RequestHolder> produceRequestHolders(long frameNumber) {
+ ArrayList<RequestHolder> holders = new ArrayList<RequestHolder>();
+ int i = 0;
+ for (CaptureRequest r : mRequests) {
+ holders.add(new RequestHolder(mRequestId, i, r, mRepeating, frameNumber + i));
+ ++i;
+ }
+ return holders;
+ }
+}
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
new file mode 100644
index 0000000..22ff9c6
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
@@ -0,0 +1,259 @@
+/*
+ * 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.legacy;
+
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.utils.CameraBinderDecorator;
+import android.os.Handler;
+import android.util.Log;
+
+/**
+ * Emulates a the state of a single Camera2 device.
+ *
+ * <p>
+ * This class acts as the state machine for a camera device. Valid state transitions are given
+ * in the table below:
+ * </p>
+ *
+ * <ul>
+ * <li>{@code UNCONFIGURED -> CONFIGURING}</li>
+ * <li>{@code CONFIGURING -> IDLE}</li>
+ * <li>{@code IDLE -> CONFIGURING}</li>
+ * <li>{@code IDLE -> CAPTURING}</li>
+ * <li>{@code CAPTURING -> IDLE}</li>
+ * <li>{@code ANY -> ERROR}</li>
+ * </ul>
+ */
+public class CameraDeviceState {
+ private static final String TAG = "CameraDeviceState";
+ private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
+
+ private static final int STATE_ERROR = 0;
+ private static final int STATE_UNCONFIGURED = 1;
+ private static final int STATE_CONFIGURING = 2;
+ private static final int STATE_IDLE = 3;
+ private static final int STATE_CAPTURING = 4;
+
+ private int mCurrentState = STATE_UNCONFIGURED;
+ private int mCurrentError = CameraBinderDecorator.NO_ERROR;
+
+ private RequestHolder mCurrentRequest = null;
+
+ private Handler mCurrentHandler = null;
+ private CameraDeviceStateListener mCurrentListener = null;
+
+
+ /**
+ * CameraDeviceStateListener callbacks to be called after state transitions.
+ */
+ public interface CameraDeviceStateListener {
+ void onError(int errorCode, RequestHolder holder);
+ void onConfiguring();
+ void onIdle();
+ void onCaptureStarted(RequestHolder holder);
+ void onCaptureResult(CameraMetadataNative result, RequestHolder holder);
+ }
+
+ /**
+ * Transition to the {@code ERROR} state.
+ *
+ * <p>
+ * The device cannot exit the {@code ERROR} state. If the device was not already in the
+ * {@code ERROR} state, {@link CameraDeviceStateListener#onError(int, RequestHolder)} will be
+ * called.
+ * </p>
+ *
+ * @param error the error to set. Should be one of the error codes defined in
+ * {@link android.hardware.camera2.utils.CameraBinderDecorator}.
+ */
+ public synchronized void setError(int error) {
+ mCurrentError = error;
+ doStateTransition(STATE_ERROR);
+ }
+
+ /**
+ * Transition to the {@code CONFIGURING} state, or {@code ERROR} if in an invalid state.
+ *
+ * <p>
+ * If the device was not already in the {@code CONFIGURING} state,
+ * {@link CameraDeviceStateListener#onConfiguring()} will be called.
+ * </p>
+ *
+ * @return {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred.
+ */
+ public synchronized int setConfiguring() {
+ doStateTransition(STATE_CONFIGURING);
+ return mCurrentError;
+ }
+
+ /**
+ * Transition to the {@code IDLE} state, or {@code ERROR} if in an invalid state.
+ *
+ * <p>
+ * If the device was not already in the {@code IDLE} state,
+ * {@link CameraDeviceStateListener#onIdle()} will be called.
+ * </p>
+ *
+ * @return {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred.
+ */
+ public synchronized int setIdle() {
+ doStateTransition(STATE_IDLE);
+ return mCurrentError;
+ }
+
+ /**
+ * Transition to the {@code CAPTURING} state, or {@code ERROR} if in an invalid state.
+ *
+ * <p>
+ * If the device was not already in the {@code CAPTURING} state,
+ * {@link CameraDeviceStateListener#onCaptureStarted(RequestHolder)} will be called.
+ * </p>
+ *
+ * @param request A {@link RequestHolder} containing the request for the current capture.
+ * @return {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred.
+ */
+ public synchronized int setCaptureStart(final RequestHolder request) {
+ mCurrentRequest = request;
+ doStateTransition(STATE_CAPTURING);
+ return mCurrentError;
+ }
+
+ /**
+ * Set the result for a capture.
+ *
+ * <p>
+ * If the device was in the {@code CAPTURING} state,
+ * {@link CameraDeviceStateListener#onCaptureResult(CameraMetadataNative, RequestHolder)} will
+ * be called with the given result, otherwise this will result in the device transitioning to
+ * the {@code ERROR} state,
+ * </p>
+ *
+ * @param request the {@link RequestHolder} request that created this result.
+ * @param result the {@link CameraMetadataNative} result to set.
+ * @return {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred.
+ */
+ public synchronized int setCaptureResult(final RequestHolder request,
+ final CameraMetadataNative result) {
+ if (mCurrentState != STATE_CAPTURING) {
+ Log.e(TAG, "Cannot receive result while in state: " + mCurrentState);
+ mCurrentError = CameraBinderDecorator.INVALID_OPERATION;
+ doStateTransition(STATE_ERROR);
+ return mCurrentError;
+ }
+
+ if (mCurrentHandler != null && mCurrentListener != null) {
+ mCurrentHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCurrentListener.onCaptureResult(result, request);
+ }
+ });
+ }
+ return mCurrentError;
+ }
+
+ /**
+ * Set the listener for state transition callbacks.
+ *
+ * @param handler handler on which to call the callbacks.
+ * @param listener the {@link CameraDeviceStateListener} callbacks to call.
+ */
+ public synchronized void setCameraDeviceCallbacks(Handler handler,
+ CameraDeviceStateListener listener) {
+ mCurrentHandler = handler;
+ mCurrentListener = listener;
+ }
+
+ private void doStateTransition(int newState) {
+ if (DEBUG) {
+ if (newState != mCurrentState) {
+ Log.d(TAG, "Transitioning to state " + newState);
+ }
+ }
+ switch(newState) {
+ case STATE_ERROR:
+ if (mCurrentState != STATE_ERROR && mCurrentHandler != null &&
+ mCurrentListener != null) {
+ mCurrentHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCurrentListener.onError(mCurrentError, mCurrentRequest);
+ }
+ });
+ }
+ mCurrentState = STATE_ERROR;
+ break;
+ case STATE_CONFIGURING:
+ if (mCurrentState != STATE_UNCONFIGURED && mCurrentState != STATE_IDLE) {
+ Log.e(TAG, "Cannot call configure while in state: " + mCurrentState);
+ mCurrentError = CameraBinderDecorator.INVALID_OPERATION;
+ doStateTransition(STATE_ERROR);
+ break;
+ }
+ if (mCurrentState != STATE_CONFIGURING && mCurrentHandler != null &&
+ mCurrentListener != null) {
+ mCurrentHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCurrentListener.onConfiguring();
+ }
+ });
+ }
+ mCurrentState = STATE_CONFIGURING;
+ break;
+ case STATE_IDLE:
+ if (mCurrentState != STATE_CONFIGURING && mCurrentState != STATE_CAPTURING) {
+ Log.e(TAG, "Cannot call idle while in state: " + mCurrentState);
+ mCurrentError = CameraBinderDecorator.INVALID_OPERATION;
+ doStateTransition(STATE_ERROR);
+ break;
+ }
+ if (mCurrentState != STATE_IDLE && mCurrentHandler != null &&
+ mCurrentListener != null) {
+ mCurrentHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCurrentListener.onIdle();
+ }
+ });
+ }
+ mCurrentState = STATE_IDLE;
+ break;
+ case STATE_CAPTURING:
+ if (mCurrentState != STATE_IDLE && mCurrentState != STATE_CAPTURING) {
+ Log.e(TAG, "Cannot call capture while in state: " + mCurrentState);
+ mCurrentError = CameraBinderDecorator.INVALID_OPERATION;
+ doStateTransition(STATE_ERROR);
+ break;
+ }
+ if (mCurrentHandler != null && mCurrentListener != null) {
+ mCurrentHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCurrentListener.onCaptureStarted(mCurrentRequest);
+ }
+ });
+ }
+ mCurrentState = STATE_CAPTURING;
+ break;
+ default:
+ throw new IllegalStateException("Transition to unknown state: " + newState);
+ }
+ }
+
+
+}
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
new file mode 100644
index 0000000..54d9c3c
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -0,0 +1,273 @@
+/*
+ * 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.legacy;
+
+import android.hardware.Camera;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.ICameraDeviceCallbacks;
+import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.utils.LongParcelable;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.utils.CameraBinderDecorator;
+import android.hardware.camera2.utils.CameraRuntimeException;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Surface;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Compatibility implementation of the Camera2 API binder interface.
+ *
+ * <p>
+ * This is intended to be called from the same process as client
+ * {@link android.hardware.camera2.CameraDevice}, and wraps a
+ * {@link android.hardware.camera2.legacy.LegacyCameraDevice} that emulates Camera2 service using
+ * the Camera1 API.
+ * </p>
+ *
+ * <p>
+ * Keep up to date with ICameraDeviceUser.aidl.
+ * </p>
+ */
+public class CameraDeviceUserShim implements ICameraDeviceUser {
+ private static final String TAG = "CameraDeviceUserShim";
+
+ private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
+
+ private final LegacyCameraDevice mLegacyDevice;
+
+ private final Object mConfigureLock = new Object();
+ private int mSurfaceIdCounter;
+ private boolean mConfiguring;
+ private final SparseArray<Surface> mSurfaces;
+
+ protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera) {
+ mLegacyDevice = legacyCamera;
+ mConfiguring = false;
+ mSurfaces = new SparseArray<Surface>();
+
+ mSurfaceIdCounter = 0;
+ }
+
+ public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks,
+ int cameraId) {
+ if (DEBUG) {
+ Log.d(TAG, "Opening shim Camera device");
+ }
+ // TODO: Move open/init into LegacyCameraDevice thread when API is switched to async.
+ Camera legacyCamera = Camera.openUninitialized();
+ int initErrors = legacyCamera.cameraInit(cameraId);
+ // Check errors old HAL initialization
+ if (Camera.checkInitErrors(initErrors)) {
+ // TODO: Map over old camera error codes. This likely involves improving the error
+ // reporting in the HAL1 connect path.
+ throw new CameraRuntimeException(CameraAccessException.CAMERA_DISCONNECTED);
+ }
+ LegacyCameraDevice device = new LegacyCameraDevice(cameraId, legacyCamera, callbacks);
+ return new CameraDeviceUserShim(cameraId, device);
+ }
+
+ @Override
+ public void disconnect() {
+ if (DEBUG) {
+ Log.d(TAG, "disconnect called.");
+ }
+ mLegacyDevice.close();
+ }
+
+ @Override
+ public int submitRequest(CaptureRequest request, boolean streaming,
+ /*out*/LongParcelable lastFrameNumber) {
+ if (DEBUG) {
+ Log.d(TAG, "submitRequest called.");
+ }
+ synchronized(mConfigureLock) {
+ if (mConfiguring) {
+ Log.e(TAG, "Cannot submit request, configuration change in progress.");
+ return CameraBinderDecorator.INVALID_OPERATION;
+ }
+ }
+ return mLegacyDevice.submitRequest(request, streaming, lastFrameNumber);
+ }
+
+ @Override
+ public int submitRequestList(List<CaptureRequest> request, boolean streaming,
+ /*out*/LongParcelable lastFrameNumber) {
+ if (DEBUG) {
+ Log.d(TAG, "submitRequestList called.");
+ }
+ synchronized(mConfigureLock) {
+ if (mConfiguring) {
+ Log.e(TAG, "Cannot submit request, configuration change in progress.");
+ return CameraBinderDecorator.INVALID_OPERATION;
+ }
+ }
+ return mLegacyDevice.submitRequestList(request, streaming, lastFrameNumber);
+ }
+
+ @Override
+ public int cancelRequest(int requestId, /*out*/LongParcelable lastFrameNumber) {
+ if (DEBUG) {
+ Log.d(TAG, "cancelRequest called.");
+ }
+ synchronized(mConfigureLock) {
+ if (mConfiguring) {
+ Log.e(TAG, "Cannot cancel request, configuration change in progress.");
+ return CameraBinderDecorator.INVALID_OPERATION;
+ }
+ }
+ long lastFrame = mLegacyDevice.cancelRequest(requestId);
+ lastFrameNumber.setNumber(lastFrame);
+ return CameraBinderDecorator.NO_ERROR;
+ }
+
+ @Override
+ public int beginConfigure() {
+ if (DEBUG) {
+ Log.d(TAG, "beginConfigure called.");
+ }
+ synchronized(mConfigureLock) {
+ if (mConfiguring) {
+ Log.e(TAG, "Cannot begin configure, configuration change already in progress.");
+ return CameraBinderDecorator.INVALID_OPERATION;
+ }
+ mConfiguring = true;
+ }
+ return CameraBinderDecorator.NO_ERROR;
+ }
+
+ @Override
+ public int endConfigure() {
+ if (DEBUG) {
+ Log.d(TAG, "endConfigure called.");
+ }
+ ArrayList<Surface> surfaces = null;
+ synchronized(mConfigureLock) {
+ if (!mConfiguring) {
+ Log.e(TAG, "Cannot end configure, no configuration change in progress.");
+ return CameraBinderDecorator.INVALID_OPERATION;
+ }
+ int numSurfaces = mSurfaces.size();
+ if (numSurfaces > 0) {
+ surfaces = new ArrayList<Surface>();
+ for (int i = 0; i < numSurfaces; ++i) {
+ surfaces.add(mSurfaces.valueAt(i));
+ }
+ }
+ mConfiguring = false;
+ }
+ return mLegacyDevice.configureOutputs(surfaces);
+ }
+
+ @Override
+ public int deleteStream(int streamId) {
+ if (DEBUG) {
+ Log.d(TAG, "deleteStream called.");
+ }
+ synchronized(mConfigureLock) {
+ if (!mConfiguring) {
+ Log.e(TAG, "Cannot delete stream, beginConfigure hasn't been called yet.");
+ return CameraBinderDecorator.INVALID_OPERATION;
+ }
+ int index = mSurfaces.indexOfKey(streamId);
+ if (index < 0) {
+ Log.e(TAG, "Cannot delete stream, stream id " + streamId + " doesn't exist.");
+ return CameraBinderDecorator.BAD_VALUE;
+ }
+ mSurfaces.removeAt(index);
+ }
+ return CameraBinderDecorator.NO_ERROR;
+ }
+
+ @Override
+ public int createStream(int width, int height, int format, Surface surface) {
+ if (DEBUG) {
+ Log.d(TAG, "createStream called.");
+ }
+ synchronized(mConfigureLock) {
+ if (!mConfiguring) {
+ Log.e(TAG, "Cannot create stream, beginConfigure hasn't been called yet.");
+ return CameraBinderDecorator.INVALID_OPERATION;
+ }
+ int id = ++mSurfaceIdCounter;
+ mSurfaces.put(id, surface);
+ return id;
+ }
+ }
+
+ @Override
+ public int createDefaultRequest(int templateId, /*out*/CameraMetadataNative request) {
+ if (DEBUG) {
+ Log.d(TAG, "createDefaultRequest called.");
+ }
+ // TODO: implement createDefaultRequest.
+ Log.e(TAG, "createDefaultRequest unimplemented.");
+ return CameraBinderDecorator.NO_ERROR;
+ }
+
+ @Override
+ public int getCameraInfo(/*out*/CameraMetadataNative info) {
+ if (DEBUG) {
+ Log.d(TAG, "getCameraInfo called.");
+ }
+ // TODO: implement getCameraInfo.
+ Log.e(TAG, "getCameraInfo unimplemented.");
+ return CameraBinderDecorator.NO_ERROR;
+ }
+
+ @Override
+ public int waitUntilIdle() throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "waitUntilIdle called.");
+ }
+ synchronized(mConfigureLock) {
+ if (mConfiguring) {
+ Log.e(TAG, "Cannot wait until idle, configuration change in progress.");
+ return CameraBinderDecorator.INVALID_OPERATION;
+ }
+ }
+ mLegacyDevice.waitUntilIdle();
+ return CameraBinderDecorator.NO_ERROR;
+ }
+
+ @Override
+ public int flush(/*out*/LongParcelable lastFrameNumber) {
+ if (DEBUG) {
+ Log.d(TAG, "flush called.");
+ }
+ synchronized(mConfigureLock) {
+ if (mConfiguring) {
+ Log.e(TAG, "Cannot flush, configuration change in progress.");
+ return CameraBinderDecorator.INVALID_OPERATION;
+ }
+ }
+ // TODO: implement flush.
+ return CameraBinderDecorator.NO_ERROR;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // This is solely intended to be used for in-process binding.
+ return null;
+ }
+}
diff --git a/core/java/android/hardware/camera2/legacy/GLThreadManager.java b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
new file mode 100644
index 0000000..3fd2309
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
@@ -0,0 +1,234 @@
+/*
+ * 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.legacy;
+
+import android.graphics.SurfaceTexture;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.Surface;
+
+import java.util.Collection;
+
+/**
+ * GLThreadManager handles the thread used for rendering into the configured output surfaces.
+ */
+public class GLThreadManager {
+ private final String TAG;
+ private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
+
+ private static final int MSG_NEW_CONFIGURATION = 1;
+ private static final int MSG_NEW_FRAME = 2;
+ private static final int MSG_CLEANUP = 3;
+ private static final int MSG_DROP_FRAMES = 4;
+ private static final int MSG_ALLOW_FRAMES = 5;
+
+ private final SurfaceTextureRenderer mTextureRenderer;
+
+ private final RequestHandlerThread mGLHandlerThread;
+
+ private final RequestThreadManager.FpsCounter mPrevCounter =
+ new RequestThreadManager.FpsCounter("GL Preview Producer");
+
+ /**
+ * Container object for Configure messages.
+ */
+ private static class ConfigureHolder {
+ public final ConditionVariable condition;
+ public final Collection<Surface> surfaces;
+
+ public ConfigureHolder(ConditionVariable condition, Collection<Surface> surfaces) {
+ this.condition = condition;
+ this.surfaces = surfaces;
+ }
+ }
+
+ private final Handler.Callback mGLHandlerCb = new Handler.Callback() {
+ private boolean mCleanup = false;
+ private boolean mConfigured = false;
+ private boolean mDroppingFrames = false;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (mCleanup) {
+ return true;
+ }
+ switch (msg.what) {
+ case MSG_NEW_CONFIGURATION:
+ ConfigureHolder configure = (ConfigureHolder) msg.obj;
+ mTextureRenderer.cleanupEGLContext();
+ mTextureRenderer.configureSurfaces(configure.surfaces);
+ configure.condition.open();
+ mConfigured = true;
+ break;
+ case MSG_NEW_FRAME:
+ if (mDroppingFrames) {
+ Log.w(TAG, "Ignoring frame.");
+ break;
+ }
+ if (DEBUG) {
+ mPrevCounter.countAndLog();
+ }
+ if (!mConfigured) {
+ Log.e(TAG, "Dropping frame, EGL context not configured!");
+ }
+ mTextureRenderer.drawIntoSurfaces((Collection<Surface>) msg.obj);
+ break;
+ case MSG_CLEANUP:
+ mTextureRenderer.cleanupEGLContext();
+ mCleanup = true;
+ mConfigured = false;
+ break;
+ case MSG_DROP_FRAMES:
+ mDroppingFrames = true;
+ break;
+ case MSG_ALLOW_FRAMES:
+ mDroppingFrames = false;
+ default:
+ Log.e(TAG, "Unhandled message " + msg.what + " on GLThread.");
+ break;
+ }
+ return true;
+ }
+ };
+
+ /**
+ * Create a new GL thread and renderer.
+ *
+ * @param cameraId the camera id for this thread.
+ */
+ public GLThreadManager(int cameraId) {
+ mTextureRenderer = new SurfaceTextureRenderer();
+ TAG = String.format("CameraDeviceGLThread-%d", cameraId);
+ mGLHandlerThread = new RequestHandlerThread(TAG, mGLHandlerCb);
+ }
+
+ /**
+ * Start the thread.
+ *
+ * <p>
+ * This must be called before queueing new frames.
+ * </p>
+ */
+ public void start() {
+ mGLHandlerThread.start();
+ }
+
+ /**
+ * Wait until the thread has started.
+ */
+ public void waitUntilStarted() {
+ mGLHandlerThread.waitUntilStarted();
+ }
+
+ /**
+ * Quit the thread.
+ *
+ * <p>
+ * No further methods can be called after this.
+ * </p>
+ */
+ public void quit() {
+ Handler handler = mGLHandlerThread.getHandler();
+ handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
+ mGLHandlerThread.quitSafely();
+ }
+
+ /**
+ * Queue a new call to draw into a given set of surfaces.
+ *
+ * <p>
+ * The set of surfaces passed here must be a subset of the set of surfaces passed in
+ * the last call to {@link #setConfigurationAndWait}.
+ * </p>
+ *
+ * @param targets a collection of {@link android.view.Surface}s to draw into.
+ */
+ public void queueNewFrame(Collection<Surface> targets) {
+ Handler handler = mGLHandlerThread.getHandler();
+
+ /**
+ * Avoid queuing more than one new frame. If we are not consuming faster than frames
+ * are produced, drop frames rather than allowing the queue to back up.
+ */
+ if (!handler.hasMessages(MSG_NEW_FRAME)) {
+ handler.sendMessage(handler.obtainMessage(MSG_NEW_FRAME, targets));
+ } else {
+ Log.e(TAG, "GLThread dropping frame. Not consuming frames quickly enough!");
+ }
+ }
+
+ /**
+ * Configure the GL renderer for the given set of output surfaces, and block until
+ * this configuration has been applied.
+ *
+ * @param surfaces a collection of {@link android.view.Surface}s to configure.
+ */
+ public void setConfigurationAndWait(Collection<Surface> surfaces) {
+ Handler handler = mGLHandlerThread.getHandler();
+
+ final ConditionVariable condition = new ConditionVariable(/*closed*/false);
+ ConfigureHolder configure = new ConfigureHolder(condition, surfaces);
+
+ Message m = handler.obtainMessage(MSG_NEW_CONFIGURATION, /*arg1*/0, /*arg2*/0, configure);
+ handler.sendMessage(m);
+
+ // Block until configuration applied.
+ condition.block();
+ }
+
+ /**
+ * Get the underlying surface to produce frames from.
+ *
+ * <p>
+ * This returns the surface that is drawn into the set of surfaces passed in for each frame.
+ * This method should only be called after a call to
+ * {@link #setConfigurationAndWait(java.util.Collection)}. Calling this before the first call
+ * to {@link #setConfigurationAndWait(java.util.Collection)}, after {@link #quit()}, or
+ * concurrently to one of these calls may result in an invalid
+ * {@link android.graphics.SurfaceTexture} being returned.
+ * </p>
+ *
+ * @return an {@link android.graphics.SurfaceTexture} to draw to.
+ */
+ public SurfaceTexture getCurrentSurfaceTexture() {
+ return mTextureRenderer.getSurfaceTexture();
+ }
+
+ /**
+ * Ignore any subsequent calls to {@link #queueNewFrame(java.util.Collection)}.
+ */
+ public void ignoreNewFrames() {
+ mGLHandlerThread.getHandler().sendEmptyMessage(MSG_DROP_FRAMES);
+ }
+
+ /**
+ * Wait until no messages are queued.
+ */
+ public void waitUntilIdle() {
+ mGLHandlerThread.waitUntilIdle();
+ }
+
+ /**
+ * Re-enable drawing new frames after a call to {@link #ignoreNewFrames()}.
+ */
+ public void allowNewFrames() {
+ mGLHandlerThread.getHandler().sendEmptyMessage(MSG_ALLOW_FRAMES);
+ }
+}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
new file mode 100644
index 0000000..f9cf905
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.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.hardware.camera2.legacy;
+
+import android.graphics.ImageFormat;
+import android.hardware.Camera;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.impl.CaptureResultExtras;
+import android.hardware.camera2.ICameraDeviceCallbacks;
+import android.hardware.camera2.utils.LongParcelable;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.utils.CameraBinderDecorator;
+import android.hardware.camera2.utils.CameraRuntimeException;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Surface;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class emulates the functionality of a Camera2 device using a the old Camera class.
+ *
+ * <p>
+ * There are two main components that are used to implement this:
+ * - A state machine containing valid Camera2 device states ({@link CameraDeviceState}).
+ * - A message-queue based pipeline that manages an old Camera class, and executes capture and
+ * configuration requests.
+ * </p>
+ */
+public class LegacyCameraDevice implements AutoCloseable {
+ public static final String DEBUG_PROP = "HAL1ShimLogging";
+
+ private final String TAG;
+
+ private final int mCameraId;
+ private final ICameraDeviceCallbacks mDeviceCallbacks;
+ private final CameraDeviceState mDeviceState = new CameraDeviceState();
+
+ private final ConditionVariable mIdle = new ConditionVariable(/*open*/true);
+ private final AtomicInteger mRequestIdCounter = new AtomicInteger(0);
+
+ private final HandlerThread mCallbackHandlerThread = new HandlerThread("ResultThread");
+ private final Handler mCallbackHandler;
+ private static final int ILLEGAL_VALUE = -1;
+
+ private CaptureResultExtras getExtrasFromRequest(RequestHolder holder) {
+ if (holder == null) {
+ return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE,
+ ILLEGAL_VALUE, ILLEGAL_VALUE);
+ }
+ return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(),
+ /*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber());
+ }
+
+ /**
+ * Listener for the camera device state machine. Calls the appropriate
+ * {@link ICameraDeviceCallbacks} for each state transition.
+ */
+ private final CameraDeviceState.CameraDeviceStateListener mStateListener =
+ new CameraDeviceState.CameraDeviceStateListener() {
+ @Override
+ public void onError(final int errorCode, RequestHolder holder) {
+ mIdle.open();
+ final CaptureResultExtras extras = getExtrasFromRequest(holder);
+ try {
+ mDeviceCallbacks.onCameraError(errorCode, extras);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception during onCameraError callback: ", e);
+ }
+
+ }
+
+ @Override
+ public void onConfiguring() {
+ // Do nothing
+ }
+
+ @Override
+ public void onIdle() {
+ mIdle.open();
+
+ try {
+ mDeviceCallbacks.onCameraIdle();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception during onCameraIdle callback: ", e);
+ }
+ }
+
+ @Override
+ public void onCaptureStarted(RequestHolder holder) {
+ final CaptureResultExtras extras = getExtrasFromRequest(holder);
+
+ try {
+ // TODO: Don't fake timestamp
+ mDeviceCallbacks.onCaptureStarted(extras, System.nanoTime());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception during onCameraError callback: ", e);
+ }
+
+ }
+
+ @Override
+ public void onCaptureResult(CameraMetadataNative result, RequestHolder holder) {
+ final CaptureResultExtras extras = getExtrasFromRequest(holder);
+
+ try {
+ // TODO: Don't fake metadata
+ mDeviceCallbacks.onResultReceived(result, extras);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception during onCameraError callback: ", e);
+ }
+ }
+ };
+
+ private final RequestThreadManager mRequestThreadManager;
+
+ /**
+ * Check if a given surface uses {@link ImageFormat#YUV_420_888} format.
+ *
+ * @param s the surface to check.
+ * @return {@code true} if the surfaces uses {@link ImageFormat#YUV_420_888}.
+ */
+ static boolean needsConversion(Surface s) {
+ return LegacyCameraDevice.nativeDetectSurfaceType(s) == ImageFormat.YUV_420_888;
+ }
+
+ /**
+ * Create a new emulated camera device from a given Camera 1 API camera.
+ *
+ * <p>
+ * The {@link Camera} provided to this constructor must already have been successfully opened,
+ * and ownership of the provided camera is passed to this object. No further calls to the
+ * camera methods should be made following this constructor.
+ * </p>
+ *
+ * @param cameraId the id of the camera.
+ * @param camera an open {@link Camera} device.
+ * @param callbacks {@link ICameraDeviceCallbacks} callbacks to call for Camera2 API operations.
+ */
+ public LegacyCameraDevice(int cameraId, Camera camera, ICameraDeviceCallbacks callbacks) {
+ mCameraId = cameraId;
+ mDeviceCallbacks = callbacks;
+ TAG = String.format("CameraDevice-%d-LE", mCameraId);
+
+ mCallbackHandlerThread.start();
+ mCallbackHandler = new Handler(mCallbackHandlerThread.getLooper());
+ mDeviceState.setCameraDeviceCallbacks(mCallbackHandler, mStateListener);
+ mRequestThreadManager =
+ new RequestThreadManager(cameraId, camera, mDeviceState);
+ mRequestThreadManager.start();
+ }
+
+ /**
+ * Configure the device with a set of output surfaces.
+ *
+ * @param outputs a list of surfaces to set.
+ * @return an error code for this binder operation, or {@link CameraBinderDecorator.NO_ERROR}
+ * on success.
+ */
+ public int configureOutputs(List<Surface> outputs) {
+ int error = mDeviceState.setConfiguring();
+ if (error == CameraBinderDecorator.NO_ERROR) {
+ mRequestThreadManager.configure(outputs);
+ error = mDeviceState.setIdle();
+ }
+ return error;
+ }
+
+ /**
+ * Submit a burst of capture requests.
+ *
+ * @param requestList a list of capture requests to execute.
+ * @param repeating {@code true} if this burst is repeating.
+ * @param frameNumber an output argument that contains either the frame number of the last frame
+ * that will be returned for this request, or the frame number of the last
+ * frame that will be returned for the current repeating request if this
+ * burst is set to be repeating.
+ * @return the request id.
+ */
+ public int submitRequestList(List<CaptureRequest> requestList, boolean repeating,
+ /*out*/LongParcelable frameNumber) {
+ // TODO: validate request here
+ mIdle.close();
+ return mRequestThreadManager.submitCaptureRequests(requestList, repeating,
+ frameNumber);
+ }
+
+ /**
+ * Submit a single capture request.
+ *
+ * @param request the capture request to execute.
+ * @param repeating {@code true} if this request is repeating.
+ * @param frameNumber an output argument that contains either the frame number of the last frame
+ * that will be returned for this request, or the frame number of the last
+ * frame that will be returned for the current repeating request if this
+ * request is set to be repeating.
+ * @return the request id.
+ */
+ public int submitRequest(CaptureRequest request, boolean repeating,
+ /*out*/LongParcelable frameNumber) {
+ ArrayList<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
+ requestList.add(request);
+ return submitRequestList(requestList, repeating, frameNumber);
+ }
+
+ /**
+ * Cancel the repeating request with the given request id.
+ *
+ * @param requestId the request id of the request to cancel.
+ * @return the last frame number to be returned from the HAL for the given repeating request, or
+ * {@code INVALID_FRAME} if none exists.
+ */
+ public long cancelRequest(int requestId) {
+ return mRequestThreadManager.cancelRepeating(requestId);
+ }
+
+ /**
+ * Block until the {@link ICameraDeviceCallbacks#onCameraIdle()} callback is received.
+ */
+ public void waitUntilIdle() {
+ mIdle.block();
+ }
+
+ @Override
+ public void close() {
+ mRequestThreadManager.quit();
+ mCallbackHandlerThread.quitSafely();
+ // TODO: throw IllegalStateException in every method after close has been called
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } catch (CameraRuntimeException e) {
+ Log.e(TAG, "Got error while trying to finalize, ignoring: " + e.getMessage());
+ } finally {
+ super.finalize();
+ }
+ }
+
+ protected static native int nativeDetectSurfaceType(Surface surface);
+
+ protected static native void nativeDetectSurfaceDimens(Surface surface, int[] dimens);
+
+ protected static native void nativeConfigureSurface(Surface surface, int width, int height,
+ int pixelFormat);
+
+ protected static native void nativeProduceFrame(Surface surface, byte[] pixelBuffer, int width,
+ int height, int pixelFormat);
+
+ protected static native void nativeSetSurfaceFormat(Surface surface, int pixelFormat);
+
+ protected static native void nativeSetSurfaceDimens(Surface surface, int width, int height);
+
+}
diff --git a/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java b/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
new file mode 100644
index 0000000..36cd907
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
@@ -0,0 +1,101 @@
+/*
+ * 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.legacy;
+
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.MessageQueue;
+
+public class RequestHandlerThread extends HandlerThread {
+ private final ConditionVariable mStarted = new ConditionVariable(false);
+ private final ConditionVariable mIdle = new ConditionVariable(true);
+ private Handler.Callback mCallback;
+ private volatile Handler mHandler;
+
+ public RequestHandlerThread(String name, Handler.Callback callback) {
+ super(name, Thread.MAX_PRIORITY);
+ mCallback = callback;
+ }
+
+ @Override
+ protected void onLooperPrepared() {
+ mHandler = new Handler(getLooper(), mCallback);
+ mStarted.open();
+ }
+
+ // Blocks until thread has started
+ public void waitUntilStarted() {
+ mStarted.block();
+ }
+
+ // May return null if the handler is not set up yet.
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ // Blocks until thread has started
+ public Handler waitAndGetHandler() {
+ waitUntilStarted();
+ return getHandler();
+ }
+
+ // Atomic multi-type message existence check
+ public boolean hasAnyMessages(int[] what) {
+ synchronized (mHandler.getLooper().getQueue()) {
+ for (int i : what) {
+ if (mHandler.hasMessages(i)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // Atomic multi-type message remove
+ public void removeMessages(int[] what) {
+ synchronized (mHandler.getLooper().getQueue()) {
+ for (int i : what) {
+ mHandler.removeMessages(i);
+ }
+ }
+ }
+
+ private final MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() {
+ @Override
+ public boolean queueIdle() {
+ mIdle.open();
+ return false;
+ }
+ };
+
+ // Blocks until thread is idling
+ public void waitUntilIdle() {
+ Looper looper = waitAndGetHandler().getLooper();
+ if (looper.isIdling()) {
+ return;
+ }
+ mIdle.close();
+ looper.getQueue().addIdleHandler(mIdleHandler);
+ if (looper.isIdling()) {
+ return;
+ }
+ mIdle.block();
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/legacy/RequestHolder.java b/core/java/android/hardware/camera2/legacy/RequestHolder.java
new file mode 100644
index 0000000..8a9052f
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/RequestHolder.java
@@ -0,0 +1,159 @@
+/*
+ * 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.legacy;
+
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.view.Surface;
+
+import java.util.Collection;
+
+/**
+ * Immutable container for a single capture request and associated information.
+ */
+public class RequestHolder {
+
+ private final boolean mRepeating;
+ private final CaptureRequest mRequest;
+ private final int mRequestId;
+ private final int mSubsequeceId;
+ private final long mFrameNumber;
+
+ RequestHolder(int requestId, int subsequenceId, CaptureRequest request, boolean repeating,
+ long frameNumber) {
+ mRepeating = repeating;
+ mRequest = request;
+ mRequestId = requestId;
+ mSubsequeceId = subsequenceId;
+ mFrameNumber = frameNumber;
+ }
+
+ /**
+ * Return the request id for the contained {@link CaptureRequest}.
+ */
+ public int getRequestId() {
+ return mRequestId;
+ }
+
+ /**
+ * Returns true if the contained request is repeating.
+ */
+ public boolean isRepeating() {
+ return mRepeating;
+ }
+
+ /**
+ * Return the subsequence id for this request.
+ */
+ public int getSubsequeceId() {
+ return mSubsequeceId;
+ }
+
+ /**
+ * Returns the frame number for this request.
+ */
+ public long getFrameNumber() {
+ return mFrameNumber;
+ }
+
+ /**
+ * Returns the contained request.
+ */
+ public CaptureRequest getRequest() {
+ return mRequest;
+ }
+
+ /**
+ * Returns a read-only collection of the surfaces targeted by the contained request.
+ */
+ public Collection<Surface> getHolderTargets() {
+ return getRequest().getTargets();
+ }
+
+ /**
+ * Returns true if any of the surfaces targeted by the contained request require jpeg buffers.
+ */
+ public boolean hasJpegTargets() {
+ for (Surface s : getHolderTargets()) {
+ if (jpegType(s)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if any of the surfaces targeted by the contained request require a
+ * non-jpeg buffer type.
+ */
+ public boolean hasPreviewTargets() {
+ for (Surface s : getHolderTargets()) {
+ if (previewType(s)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the first surface targeted by the contained request that requires a
+ * non-jpeg buffer type.
+ */
+ public Surface getFirstPreviewTarget() {
+ for (Surface s : getHolderTargets()) {
+ if (previewType(s)) {
+ return s;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the given surface requires jpeg buffers.
+ *
+ * @param s a {@link Surface} to check.
+ * @return true if the surface requires a jpeg buffer.
+ */
+ public static boolean jpegType(Surface s) {
+ if (LegacyCameraDevice.nativeDetectSurfaceType(s) ==
+ CameraMetadataNative.NATIVE_JPEG_FORMAT) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the given surface requires non-jpeg buffer types.
+ *
+ * <p>
+ * "Jpeg buffer" refers to the buffers returned in the jpeg
+ * {@link android.hardware.Camera.PictureCallback}. Non-jpeg buffers are created using a tee
+ * of the preview stream drawn to the surface
+ * set via {@link android.hardware.Camera#setPreviewDisplay(android.view.SurfaceHolder)} or
+ * equivalent methods.
+ * </p>
+ * @param s a {@link Surface} to check.
+ * @return true if the surface requires a non-jpeg buffer type.
+ */
+ public static boolean previewType(Surface s) {
+ if (LegacyCameraDevice.nativeDetectSurfaceType(s) !=
+ CameraMetadataNative.NATIVE_JPEG_FORMAT) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/core/java/android/hardware/camera2/legacy/RequestQueue.java b/core/java/android/hardware/camera2/legacy/RequestQueue.java
new file mode 100644
index 0000000..5c68303
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/RequestQueue.java
@@ -0,0 +1,132 @@
+/*
+ * 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.legacy;
+
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.utils.LongParcelable;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayDeque;
+import java.util.List;
+
+/**
+ * A queue of bursts of requests.
+ *
+ * <p>This queue maintains the count of frames that have been produced, and is thread safe.</p>
+ */
+public class RequestQueue {
+ private static final String TAG = "RequestQueue";
+
+ private static final long INVALID_FRAME = -1;
+
+ private BurstHolder mRepeatingRequest = null;
+ private final ArrayDeque<BurstHolder> mRequestQueue = new ArrayDeque<BurstHolder>();
+
+ private long mCurrentFrameNumber = 0;
+ private long mCurrentRepeatingFrameNumber = INVALID_FRAME;
+ private int mCurrentRequestId = 0;
+
+ public RequestQueue() {}
+
+ /**
+ * Return and remove the next burst on the queue.
+ *
+ * <p>If a repeating burst is returned, it will not be removed.</p>
+ *
+ * @return a pair containing the next burst and the current frame number, or null if none exist.
+ */
+ public synchronized Pair<BurstHolder, Long> getNext() {
+ BurstHolder next = mRequestQueue.poll();
+ if (next == null && mRepeatingRequest != null) {
+ next = mRepeatingRequest;
+ mCurrentRepeatingFrameNumber = mCurrentFrameNumber +
+ next.getNumberOfRequests();
+ }
+
+ if (next == null) {
+ return null;
+ }
+
+ Pair<BurstHolder, Long> ret = new Pair<BurstHolder, Long>(next, mCurrentFrameNumber);
+ mCurrentFrameNumber += next.getNumberOfRequests();
+ return ret;
+ }
+
+ /**
+ * Cancel a repeating request.
+ *
+ * @param requestId the id of the repeating request to cancel.
+ * @return the last frame to be returned from the HAL for the given repeating request, or
+ * {@code INVALID_FRAME} if none exists.
+ */
+ public synchronized long stopRepeating(int requestId) {
+ long ret = INVALID_FRAME;
+ if (mRepeatingRequest != null && mRepeatingRequest.getRequestId() == requestId) {
+ mRepeatingRequest = null;
+ ret = mCurrentRepeatingFrameNumber;
+ mCurrentRepeatingFrameNumber = INVALID_FRAME;
+ } else {
+ Log.e(TAG, "cancel failed: no repeating request exists for request id: " + requestId);
+ }
+ return ret;
+ }
+
+ /**
+ * Add a the given burst to the queue.
+ *
+ * <p>If the burst is repeating, replace the current repeating burst.</p>
+ *
+ * @param requests the burst of requests to add to the queue.
+ * @param repeating true if the burst is repeating.
+ * @param frameNumber an output argument that contains either the frame number of the last frame
+ * that will be returned for this request, or the frame number of the last
+ * frame that will be returned for the current repeating request if this
+ * burst is set to be repeating.
+ * @return the request id.
+ */
+ public synchronized int submit(List<CaptureRequest> requests, boolean repeating,
+ /*out*/LongParcelable frameNumber) {
+ int requestId = mCurrentRequestId++;
+ BurstHolder burst = new BurstHolder(requestId, repeating, requests);
+ long ret = INVALID_FRAME;
+ if (burst.isRepeating()) {
+ if (mRepeatingRequest != null) {
+ ret = mCurrentRepeatingFrameNumber;
+ }
+ mCurrentRepeatingFrameNumber = INVALID_FRAME;
+ mRepeatingRequest = burst;
+ } else {
+ mRequestQueue.offer(burst);
+ ret = calculateLastFrame(burst.getRequestId());
+ }
+ frameNumber.setNumber(ret);
+ return requestId;
+ }
+
+ private long calculateLastFrame(int requestId) {
+ long total = mCurrentFrameNumber;
+ for (BurstHolder b : mRequestQueue) {
+ total += b.getNumberOfRequests();
+ if (b.getRequestId() == requestId) {
+ return total;
+ }
+ }
+ throw new IllegalStateException(
+ "At least one request must be in the queue to calculate frame number");
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
new file mode 100644
index 0000000..c4669f5
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -0,0 +1,491 @@
+/*
+ * 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.legacy;
+
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.utils.LongParcelable;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.Pair;
+import android.view.Surface;
+
+import java.io.IOError;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * This class executes requests to the {@link Camera}.
+ *
+ * <p>
+ * The main components of this class are:
+ * - A message queue of requests to the {@link Camera}.
+ * - A thread that consumes requests to the {@link Camera} and executes them.
+ * - A {@link GLThreadManager} that draws to the configured output {@link Surface}s.
+ * - An {@link CameraDeviceState} state machine that manages the callbacks for various operations.
+ * </p>
+ */
+public class RequestThreadManager {
+ private final String TAG;
+ private final int mCameraId;
+ private final RequestHandlerThread mRequestThread;
+
+ private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
+ private final Camera mCamera;
+
+ private final CameraDeviceState mDeviceState;
+
+ private static final int MSG_CONFIGURE_OUTPUTS = 1;
+ private static final int MSG_SUBMIT_CAPTURE_REQUEST = 2;
+ private static final int MSG_CLEANUP = 3;
+
+ private static final int PREVIEW_FRAME_TIMEOUT = 300; // ms
+ private static final int JPEG_FRAME_TIMEOUT = 1000; // ms
+
+ private boolean mPreviewRunning = false;
+
+ private volatile RequestHolder mInFlightPreview;
+ private volatile RequestHolder mInFlightJpeg;
+
+ private List<Surface> mPreviewOutputs = new ArrayList<Surface>();
+ private List<Surface> mCallbackOutputs = new ArrayList<Surface>();
+ private GLThreadManager mGLThreadManager;
+ private SurfaceTexture mPreviewTexture;
+
+ private final RequestQueue mRequestQueue = new RequestQueue();
+ private SurfaceTexture mDummyTexture;
+ private Surface mDummySurface;
+
+ private final FpsCounter mPrevCounter = new FpsCounter("Incoming Preview");
+
+ /**
+ * Container object for Configure messages.
+ */
+ private static class ConfigureHolder {
+ public final ConditionVariable condition;
+ public final Collection<Surface> surfaces;
+
+ public ConfigureHolder(ConditionVariable condition, Collection<Surface> surfaces) {
+ this.condition = condition;
+ this.surfaces = surfaces;
+ }
+ }
+
+ /**
+ * Counter class used to calculate and log the current FPS of frame production.
+ */
+ public static class FpsCounter {
+ //TODO: Hook this up to SystTrace?
+ private static final String TAG = "FpsCounter";
+ private int mFrameCount = 0;
+ private long mLastTime = 0;
+ private long mLastPrintTime = 0;
+ private double mLastFps = 0;
+ private final String mStreamType;
+ private static final long NANO_PER_SECOND = 1000000000; //ns
+
+ public FpsCounter(String streamType) {
+ mStreamType = streamType;
+ }
+
+ public synchronized void countFrame() {
+ mFrameCount++;
+ long nextTime = SystemClock.elapsedRealtimeNanos();
+ if (mLastTime == 0) {
+ mLastTime = nextTime;
+ }
+ if (nextTime > mLastTime + NANO_PER_SECOND) {
+ long elapsed = nextTime - mLastTime;
+ mLastFps = mFrameCount * (NANO_PER_SECOND / (double) elapsed);
+ mFrameCount = 0;
+ mLastTime = nextTime;
+ }
+ }
+
+ public synchronized double checkFps() {
+ return mLastFps;
+ }
+
+ public synchronized void staggeredLog() {
+ if (mLastTime > mLastPrintTime + 5 * NANO_PER_SECOND) {
+ mLastPrintTime = mLastTime;
+ Log.d(TAG, "FPS for " + mStreamType + " stream: " + mLastFps );
+ }
+ }
+
+ public synchronized void countAndLog() {
+ countFrame();
+ staggeredLog();
+ }
+ }
+ /**
+ * Fake preview for jpeg captures when there is no active preview
+ */
+ private void createDummySurface() {
+ if (mDummyTexture == null || mDummySurface == null) {
+ mDummyTexture = new SurfaceTexture(/*ignored*/0);
+ // TODO: use smallest default sizes
+ mDummyTexture.setDefaultBufferSize(640, 480);
+ mDummySurface = new Surface(mDummyTexture);
+ }
+ }
+
+ private final ConditionVariable mReceivedJpeg = new ConditionVariable(false);
+ private final ConditionVariable mReceivedPreview = new ConditionVariable(false);
+
+ private final Camera.PictureCallback mJpegCallback = new Camera.PictureCallback() {
+ @Override
+ public void onPictureTaken(byte[] data, Camera camera) {
+ Log.i(TAG, "Received jpeg.");
+ RequestHolder holder = mInFlightJpeg;
+ if (holder == null) {
+ Log.w(TAG, "Dropping jpeg frame.");
+ mInFlightJpeg = null;
+ return;
+ }
+ for (Surface s : holder.getHolderTargets()) {
+ if (RequestHolder.jpegType(s)) {
+ Log.i(TAG, "Producing jpeg buffer...");
+ LegacyCameraDevice.nativeSetSurfaceDimens(s, data.length, /*height*/1);
+ LegacyCameraDevice.nativeProduceFrame(s, data, data.length, /*height*/1,
+ CameraMetadataNative.NATIVE_JPEG_FORMAT);
+ }
+ }
+ mReceivedJpeg.open();
+ }
+ };
+
+ private final SurfaceTexture.OnFrameAvailableListener mPreviewCallback =
+ new SurfaceTexture.OnFrameAvailableListener() {
+ @Override
+ public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+ if (DEBUG) {
+ mPrevCounter.countAndLog();
+ }
+ RequestHolder holder = mInFlightPreview;
+ if (holder == null) {
+ Log.w(TAG, "Dropping preview frame.");
+ mInFlightPreview = null;
+ return;
+ }
+ if (holder.hasPreviewTargets()) {
+ mGLThreadManager.queueNewFrame(holder.getHolderTargets());
+ }
+
+ mReceivedPreview.open();
+ }
+ };
+
+ private void stopPreview() {
+ if (mPreviewRunning) {
+ mCamera.stopPreview();
+ mPreviewRunning = false;
+ }
+ }
+
+ private void startPreview() {
+ if (!mPreviewRunning) {
+ mCamera.startPreview();
+ mPreviewRunning = true;
+ }
+ }
+
+ private void doJpegCapture(RequestHolder request) throws IOException {
+ if (!mPreviewRunning) {
+ createDummySurface();
+ mCamera.setPreviewTexture(mDummyTexture);
+ startPreview();
+ }
+ mInFlightJpeg = request;
+ // TODO: Hook up shutter callback to CameraDeviceStateListener#onCaptureStarted
+ mCamera.takePicture(/*shutter*/null, /*raw*/null, mJpegCallback);
+ mPreviewRunning = false;
+ }
+
+ private void doPreviewCapture(RequestHolder request) throws IOException {
+ mInFlightPreview = request;
+ if (mPreviewRunning) {
+ return; // Already running
+ }
+
+ mPreviewTexture.setDefaultBufferSize(640, 480); // TODO: size selection based on request
+ mCamera.setPreviewTexture(mPreviewTexture);
+ Camera.Parameters params = mCamera.getParameters();
+ List<int[]> supportedFpsRanges = params.getSupportedPreviewFpsRange();
+ int[] bestRange = getPhotoPreviewFpsRange(supportedFpsRanges);
+ if (DEBUG) {
+ Log.d(TAG, "doPreviewCapture - Selected range [" +
+ bestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] + "," +
+ bestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] + "]");
+ }
+ params.setPreviewFpsRange(bestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+ bestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+ params.setRecordingHint(true);
+ mCamera.setParameters(params);
+
+ startPreview();
+ }
+
+ private void configureOutputs(Collection<Surface> outputs) throws IOException {
+ stopPreview();
+ if (mGLThreadManager != null) {
+ mGLThreadManager.waitUntilStarted();
+ mGLThreadManager.ignoreNewFrames();
+ mGLThreadManager.waitUntilIdle();
+ }
+ mPreviewOutputs.clear();
+ mCallbackOutputs.clear();
+ mPreviewTexture = null;
+ mInFlightPreview = null;
+ mInFlightJpeg = null;
+
+ for (Surface s : outputs) {
+ int format = LegacyCameraDevice.nativeDetectSurfaceType(s);
+ switch (format) {
+ case CameraMetadataNative.NATIVE_JPEG_FORMAT:
+ mCallbackOutputs.add(s);
+ break;
+ default:
+ mPreviewOutputs.add(s);
+ break;
+ }
+ }
+
+ // TODO: Detect and optimize single-output paths here to skip stream teeing.
+ if (mGLThreadManager == null) {
+ mGLThreadManager = new GLThreadManager(mCameraId);
+ mGLThreadManager.start();
+ }
+ mGLThreadManager.waitUntilStarted();
+ mGLThreadManager.setConfigurationAndWait(mPreviewOutputs);
+ mGLThreadManager.allowNewFrames();
+ mPreviewTexture = mGLThreadManager.getCurrentSurfaceTexture();
+ mPreviewTexture.setOnFrameAvailableListener(mPreviewCallback);
+ }
+
+ // Calculate the highest FPS range supported
+ private int[] getPhotoPreviewFpsRange(List<int[]> frameRates) {
+ if (frameRates.size() == 0) {
+ Log.e(TAG, "No supported frame rates returned!");
+ return null;
+ }
+
+ int bestMin = 0;
+ int bestMax = 0;
+ int bestIndex = 0;
+ int index = 0;
+ for (int[] rate : frameRates) {
+ int minFps = rate[Camera.Parameters.PREVIEW_FPS_MIN_INDEX];
+ int maxFps = rate[Camera.Parameters.PREVIEW_FPS_MAX_INDEX];
+ if (maxFps > bestMax || (maxFps == bestMax && minFps > bestMin)) {
+ bestMin = minFps;
+ bestMax = maxFps;
+ bestIndex = index;
+ }
+ index++;
+ }
+
+ return frameRates.get(bestIndex);
+ }
+
+ private final Handler.Callback mRequestHandlerCb = new Handler.Callback() {
+ private boolean mCleanup = false;
+ private List<RequestHolder> mRepeating = null;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (mCleanup) {
+ return true;
+ }
+
+ switch (msg.what) {
+ case MSG_CONFIGURE_OUTPUTS:
+ ConfigureHolder config = (ConfigureHolder) msg.obj;
+ Log.i(TAG, "Configure outputs: " + config.surfaces.size() +
+ " surfaces configured.");
+ try {
+ configureOutputs(config.surfaces);
+ } catch (IOException e) {
+ // TODO: report error to CameraDevice
+ throw new IOError(e);
+ }
+ config.condition.open();
+ break;
+ case MSG_SUBMIT_CAPTURE_REQUEST:
+ Handler handler = RequestThreadManager.this.mRequestThread.getHandler();
+
+ // Get the next burst from the request queue.
+ Pair<BurstHolder, Long> nextBurst = mRequestQueue.getNext();
+ if (nextBurst == null) {
+ mDeviceState.setIdle();
+ stopPreview();
+ break;
+ } else {
+ // Queue another capture if we did not get the last burst.
+ handler.sendEmptyMessage(MSG_SUBMIT_CAPTURE_REQUEST);
+ }
+
+ // Complete each request in the burst
+ List<RequestHolder> requests =
+ nextBurst.first.produceRequestHolders(nextBurst.second);
+ for (RequestHolder holder : requests) {
+ mDeviceState.setCaptureStart(holder);
+ try {
+ if (holder.hasPreviewTargets()) {
+ mReceivedPreview.close();
+ doPreviewCapture(holder);
+ if (!mReceivedPreview.block(PREVIEW_FRAME_TIMEOUT)) {
+ // TODO: report error to CameraDevice
+ Log.e(TAG, "Hit timeout for preview callback!");
+ }
+ }
+ if (holder.hasJpegTargets()) {
+ mReceivedJpeg.close();
+ doJpegCapture(holder);
+ mReceivedJpeg.block();
+ if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) {
+ // TODO: report error to CameraDevice
+ Log.e(TAG, "Hit timeout for jpeg callback!");
+ }
+ mInFlightJpeg = null;
+ }
+ } catch (IOException e) {
+ // TODO: err handling
+ throw new IOError(e);
+ }
+ // TODO: Set fields in result.
+ mDeviceState.setCaptureResult(holder, new CameraMetadataNative());
+ }
+ break;
+ case MSG_CLEANUP:
+ mCleanup = true;
+ if (mGLThreadManager != null) {
+ mGLThreadManager.quit();
+ }
+ if (mCamera != null) {
+ mCamera.release();
+ }
+ break;
+ default:
+ throw new AssertionError("Unhandled message " + msg.what +
+ " on RequestThread.");
+ }
+ return true;
+ }
+ };
+
+ /**
+ * Create a new RequestThreadManager.
+ *
+ * @param cameraId the id of the camera to use.
+ * @param camera an open camera object. The RequestThreadManager takes ownership of this camera
+ * object, and is responsible for closing it.
+ * @param deviceState a {@link CameraDeviceState} state machine.
+ */
+ public RequestThreadManager(int cameraId, Camera camera,
+ CameraDeviceState deviceState) {
+ mCamera = camera;
+ mCameraId = cameraId;
+ String name = String.format("RequestThread-%d", cameraId);
+ TAG = name;
+ mDeviceState = deviceState;
+ mRequestThread = new RequestHandlerThread(name, mRequestHandlerCb);
+ }
+
+ /**
+ * Start the request thread.
+ */
+ public void start() {
+ mRequestThread.start();
+ }
+
+ /**
+ * Flush the pending requests.
+ */
+ public void flush() {
+ // TODO: Implement flush.
+ Log.e(TAG, "flush not yet implemented.");
+ }
+
+ /**
+ * Quit the request thread, and clean up everything.
+ */
+ public void quit() {
+ Handler handler = mRequestThread.waitAndGetHandler();
+ handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
+ mRequestThread.quitSafely();
+ }
+
+ /**
+ * Submit the given burst of requests to be captured.
+ *
+ * <p>If the burst is repeating, replace the current repeating burst.</p>
+ *
+ * @param requests the burst of requests to add to the queue.
+ * @param repeating true if the burst is repeating.
+ * @param frameNumber an output argument that contains either the frame number of the last frame
+ * that will be returned for this request, or the frame number of the last
+ * frame that will be returned for the current repeating request if this
+ * burst is set to be repeating.
+ * @return the request id.
+ */
+ public int submitCaptureRequests(List<CaptureRequest> requests, boolean repeating,
+ /*out*/LongParcelable frameNumber) {
+ Handler handler = mRequestThread.waitAndGetHandler();
+ int ret = mRequestQueue.submit(requests, repeating, frameNumber);
+ handler.sendEmptyMessage(MSG_SUBMIT_CAPTURE_REQUEST);
+ return ret;
+ }
+
+ /**
+ * Cancel a repeating request.
+ *
+ * @param requestId the id of the repeating request to cancel.
+ * @return the last frame to be returned from the HAL for the given repeating request, or
+ * {@code INVALID_FRAME} if none exists.
+ */
+ public long cancelRepeating(int requestId) {
+ return mRequestQueue.stopRepeating(requestId);
+ }
+
+
+ /**
+ * Configure with the current output Surfaces.
+ *
+ * <p>
+ * This operation blocks until the configuration is complete.
+ * </p>
+ *
+ * @param outputs a {@link java.util.Collection} of outputs to configure.
+ */
+ public void configure(Collection<Surface> outputs) {
+ Handler handler = mRequestThread.waitAndGetHandler();
+ final ConditionVariable condition = new ConditionVariable(/*closed*/false);
+ ConfigureHolder holder = new ConfigureHolder(condition, outputs);
+ handler.sendMessage(handler.obtainMessage(MSG_CONFIGURE_OUTPUTS, 0, 0, holder));
+ condition.block();
+ }
+}
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
new file mode 100644
index 0000000..2f0f6bc
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -0,0 +1,522 @@
+/*
+* 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.legacy;
+
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.opengl.EGL14;
+import android.opengl.EGLConfig;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.EGLSurface;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+import android.util.Log;
+import android.view.Surface;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A renderer class that manages the GL state, and can draw a frame into a set of output
+ * {@link Surface}s.
+ */
+public class SurfaceTextureRenderer {
+ private static final String TAG = SurfaceTextureRenderer.class.getSimpleName();
+ private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
+ private static final int EGL_RECORDABLE_ANDROID = 0x3142; // from EGL/eglext.h
+ private static final int GL_MATRIX_SIZE = 16;
+ private static final int VERTEX_POS_SIZE = 3;
+ private static final int VERTEX_UV_SIZE = 2;
+ private static final int EGL_COLOR_BITLENGTH = 8;
+ private static final int GLES_VERSION = 2;
+ private static final int PBUFFER_PIXEL_BYTES = 4;
+
+ private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
+ private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
+ private EGLConfig mConfigs;
+
+ private class EGLSurfaceHolder {
+ Surface surface;
+ EGLSurface eglSurface;
+ int width;
+ int height;
+ }
+
+ private List<EGLSurfaceHolder> mSurfaces = new ArrayList<EGLSurfaceHolder>();
+ private List<EGLSurfaceHolder> mConversionSurfaces = new ArrayList<EGLSurfaceHolder>();
+
+ private ByteBuffer mPBufferPixels;
+
+ // Hold this to avoid GC
+ private volatile SurfaceTexture mSurfaceTexture;
+
+ private static final int FLOAT_SIZE_BYTES = 4;
+ private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
+ private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
+ private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
+ private final float[] mTriangleVerticesData = {
+ // X, Y, Z, U, V
+ -1.0f, -1.0f, 0, 0.f, 0.f,
+ 1.0f, -1.0f, 0, 1.f, 0.f,
+ -1.0f, 1.0f, 0, 0.f, 1.f,
+ 1.0f, 1.0f, 0, 1.f, 1.f,
+ };
+
+ private FloatBuffer mTriangleVertices;
+
+ /**
+ * As used in this file, this vertex shader maps a unit square to the view, and
+ * tells the fragment shader to interpolate over it. Each surface pixel position
+ * is mapped to a 2D homogeneous texture coordinate of the form (s, t, 0, 1) with
+ * s and t in the inclusive range [0, 1], and the matrix from
+ * {@link SurfaceTexture#getTransformMatrix(float[])} is used to map this
+ * coordinate to a texture location.
+ */
+ private static final String VERTEX_SHADER =
+ "uniform mat4 uMVPMatrix;\n" +
+ "uniform mat4 uSTMatrix;\n" +
+ "attribute vec4 aPosition;\n" +
+ "attribute vec4 aTextureCoord;\n" +
+ "varying vec2 vTextureCoord;\n" +
+ "void main() {\n" +
+ " gl_Position = uMVPMatrix * aPosition;\n" +
+ " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
+ "}\n";
+
+ /**
+ * This fragment shader simply draws the color in the 2D texture at
+ * the location from the {@code VERTEX_SHADER}.
+ */
+ private static final String FRAGMENT_SHADER =
+ "#extension GL_OES_EGL_image_external : require\n" +
+ "precision mediump float;\n" +
+ "varying vec2 vTextureCoord;\n" +
+ "uniform samplerExternalOES sTexture;\n" +
+ "void main() {\n" +
+ " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
+ "}\n";
+
+ private float[] mMVPMatrix = new float[GL_MATRIX_SIZE];
+ private float[] mSTMatrix = new float[GL_MATRIX_SIZE];
+
+ private int mProgram;
+ private int mTextureID = 0;
+ private int muMVPMatrixHandle;
+ private int muSTMatrixHandle;
+ private int maPositionHandle;
+ private int maTextureHandle;
+
+ public SurfaceTextureRenderer() {
+ mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length *
+ FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
+ mTriangleVertices.put(mTriangleVerticesData).position(0);
+ Matrix.setIdentityM(mSTMatrix, 0);
+ }
+
+ private int loadShader(int shaderType, String source) {
+ int shader = GLES20.glCreateShader(shaderType);
+ checkGlError("glCreateShader type=" + shaderType);
+ GLES20.glShaderSource(shader, source);
+ GLES20.glCompileShader(shader);
+ int[] compiled = new int[1];
+ GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+ if (compiled[0] == 0) {
+ Log.e(TAG, "Could not compile shader " + shaderType + ":");
+ Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
+ GLES20.glDeleteShader(shader);
+ // TODO: handle this more gracefully
+ throw new IllegalStateException("Could not compile shader " + shaderType);
+ }
+ return shader;
+ }
+
+ private int createProgram(String vertexSource, String fragmentSource) {
+ int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
+ if (vertexShader == 0) {
+ return 0;
+ }
+ int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
+ if (pixelShader == 0) {
+ return 0;
+ }
+
+ int program = GLES20.glCreateProgram();
+ checkGlError("glCreateProgram");
+ if (program == 0) {
+ Log.e(TAG, "Could not create program");
+ }
+ GLES20.glAttachShader(program, vertexShader);
+ checkGlError("glAttachShader");
+ GLES20.glAttachShader(program, pixelShader);
+ checkGlError("glAttachShader");
+ GLES20.glLinkProgram(program);
+ int[] linkStatus = new int[1];
+ GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
+ if (linkStatus[0] != GLES20.GL_TRUE) {
+ Log.e(TAG, "Could not link program: ");
+ Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+ GLES20.glDeleteProgram(program);
+ // TODO: handle this more gracefully
+ throw new IllegalStateException("Could not link program");
+ }
+ return program;
+ }
+
+ private void drawFrame(SurfaceTexture st) {
+ checkGlError("onDrawFrame start");
+ st.getTransformMatrix(mSTMatrix);
+
+ if (DEBUG) {
+ GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
+ }
+
+ GLES20.glUseProgram(mProgram);
+ checkGlError("glUseProgram");
+
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
+
+ mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+ GLES20.glVertexAttribPointer(maPositionHandle, VERTEX_POS_SIZE, GLES20.GL_FLOAT,
+ /*normalized*/ false,TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+ checkGlError("glVertexAttribPointer maPosition");
+ GLES20.glEnableVertexAttribArray(maPositionHandle);
+ checkGlError("glEnableVertexAttribArray maPositionHandle");
+
+ mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+ GLES20.glVertexAttribPointer(maTextureHandle, VERTEX_UV_SIZE, GLES20.GL_FLOAT,
+ /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+ checkGlError("glVertexAttribPointer maTextureHandle");
+ GLES20.glEnableVertexAttribArray(maTextureHandle);
+ checkGlError("glEnableVertexAttribArray maTextureHandle");
+
+ Matrix.setIdentityM(mMVPMatrix, 0);
+ GLES20.glUniformMatrix4fv(muMVPMatrixHandle, /*count*/ 1, /*transpose*/ false, mMVPMatrix,
+ /*offset*/ 0);
+ GLES20.glUniformMatrix4fv(muSTMatrixHandle, /*count*/ 1, /*transpose*/ false, mSTMatrix,
+ /*offset*/ 0);
+
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /*offset*/ 0, /*count*/ 4);
+ checkGlError("glDrawArrays");
+ GLES20.glFinish();
+ }
+
+ /**
+ * Initializes GL state. Call this after the EGL surface has been created and made current.
+ */
+ private void initializeGLState() {
+ mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
+ if (mProgram == 0) {
+ throw new IllegalStateException("failed creating program");
+ }
+ maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
+ checkGlError("glGetAttribLocation aPosition");
+ if (maPositionHandle == -1) {
+ throw new IllegalStateException("Could not get attrib location for aPosition");
+ }
+ maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
+ checkGlError("glGetAttribLocation aTextureCoord");
+ if (maTextureHandle == -1) {
+ throw new IllegalStateException("Could not get attrib location for aTextureCoord");
+ }
+
+ muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
+ checkGlError("glGetUniformLocation uMVPMatrix");
+ if (muMVPMatrixHandle == -1) {
+ throw new IllegalStateException("Could not get attrib location for uMVPMatrix");
+ }
+
+ muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
+ checkGlError("glGetUniformLocation uSTMatrix");
+ if (muSTMatrixHandle == -1) {
+ throw new IllegalStateException("Could not get attrib location for uSTMatrix");
+ }
+
+ int[] textures = new int[1];
+ GLES20.glGenTextures(/*n*/ 1, textures, /*offset*/ 0);
+
+ mTextureID = textures[0];
+ GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
+ checkGlError("glBindTexture mTextureID");
+
+ GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
+ GLES20.GL_NEAREST);
+ GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
+ GLES20.GL_LINEAR);
+ GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
+ GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
+ GLES20.GL_CLAMP_TO_EDGE);
+ checkGlError("glTexParameter");
+ }
+
+ private int getTextureId() {
+ return mTextureID;
+ }
+
+ private void clearState() {
+ mSurfaces.clear();
+ mConversionSurfaces.clear();
+ mPBufferPixels = null;
+ mSurfaceTexture = null;
+ }
+
+ private void configureEGLContext() {
+ mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+ if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
+ throw new IllegalStateException("No EGL14 display");
+ }
+ int[] version = new int[2];
+ if (!EGL14.eglInitialize(mEGLDisplay, version, /*offset*/ 0, version, /*offset*/ 1)) {
+ throw new IllegalStateException("Cannot initialize EGL14");
+ }
+
+ int[] attribList = {
+ EGL14.EGL_RED_SIZE, EGL_COLOR_BITLENGTH,
+ EGL14.EGL_GREEN_SIZE, EGL_COLOR_BITLENGTH,
+ EGL14.EGL_BLUE_SIZE, EGL_COLOR_BITLENGTH,
+ EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
+ EGL_RECORDABLE_ANDROID, 1,
+ EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT | EGL14.EGL_WINDOW_BIT,
+ EGL14.EGL_NONE
+ };
+ EGLConfig[] configs = new EGLConfig[1];
+ int[] numConfigs = new int[1];
+ EGL14.eglChooseConfig(mEGLDisplay, attribList, /*offset*/ 0, configs, /*offset*/ 0,
+ configs.length, numConfigs, /*offset*/ 0);
+ checkEglError("eglCreateContext RGB888+recordable ES2");
+ mConfigs = configs[0];
+ int[] attrib_list = {
+ EGL14.EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION,
+ EGL14.EGL_NONE
+ };
+ mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
+ attrib_list, /*offset*/ 0);
+ checkEglError("eglCreateContext");
+ if(mEGLContext == EGL14.EGL_NO_CONTEXT) {
+ throw new IllegalStateException("No EGLContext could be made");
+ }
+ }
+
+ private void configureEGLOutputSurfaces(Collection<EGLSurfaceHolder> surfaces) {
+ if (surfaces == null || surfaces.size() == 0) {
+ throw new IllegalStateException("No Surfaces were provided to draw to");
+ }
+ int[] surfaceAttribs = {
+ EGL14.EGL_NONE
+ };
+ for (EGLSurfaceHolder holder : surfaces) {
+ holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs, holder.surface,
+ surfaceAttribs, 0);
+ checkEglError("eglCreateWindowSurface");
+ }
+ }
+
+ private void configureEGLPbufferSurfaces(Collection<EGLSurfaceHolder> surfaces) {
+ if (surfaces == null || surfaces.size() == 0) {
+ throw new IllegalStateException("No Surfaces were provided to draw to");
+ }
+
+ int maxLength = 0;
+ int[] dimens = new int[2];
+ for (EGLSurfaceHolder holder : surfaces) {
+ LegacyCameraDevice.nativeDetectSurfaceDimens(holder.surface, dimens);
+ int length = dimens[0] * dimens[1];
+ // Find max surface size, ensure PBuffer can hold this many pixels
+ maxLength = (length > maxLength) ? length : maxLength;
+ int[] surfaceAttribs = {
+ EGL14.EGL_WIDTH, dimens[0],
+ EGL14.EGL_HEIGHT, dimens[1],
+ EGL14.EGL_NONE
+ };
+ holder.width = dimens[0];
+ holder.height = dimens[1];
+ holder.eglSurface =
+ EGL14.eglCreatePbufferSurface(mEGLDisplay, mConfigs, surfaceAttribs, 0);
+ checkEglError("eglCreatePbufferSurface");
+ }
+ mPBufferPixels = ByteBuffer.allocateDirect(maxLength * PBUFFER_PIXEL_BYTES)
+ .order(ByteOrder.nativeOrder());
+ }
+
+ private void releaseEGLContext() {
+ if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
+ EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
+ EGL14.EGL_NO_CONTEXT);
+ if (mSurfaces != null) {
+ for (EGLSurfaceHolder holder : mSurfaces) {
+ if (holder.eglSurface != null) {
+ EGL14.eglDestroySurface(mEGLDisplay, holder.eglSurface);
+ }
+ }
+ }
+ if (mConversionSurfaces != null) {
+ for (EGLSurfaceHolder holder : mConversionSurfaces) {
+ if (holder.eglSurface != null) {
+ EGL14.eglDestroySurface(mEGLDisplay, holder.eglSurface);
+ }
+ }
+ }
+ EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
+ EGL14.eglReleaseThread();
+ EGL14.eglTerminate(mEGLDisplay);
+ }
+
+ mConfigs = null;
+ mEGLDisplay = EGL14.EGL_NO_DISPLAY;
+ mEGLContext = EGL14.EGL_NO_CONTEXT;
+ clearState();
+ }
+
+ private void makeCurrent(EGLSurface surface) {
+ EGL14.eglMakeCurrent(mEGLDisplay, surface, surface, mEGLContext);
+ checkEglError("makeCurrent");
+ }
+
+ private boolean swapBuffers(EGLSurface surface) {
+ boolean result = EGL14.eglSwapBuffers(mEGLDisplay, surface);
+ checkEglError("swapBuffers");
+ return result;
+ }
+
+ private void checkEglError(String msg) {
+ int error;
+ if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
+ throw new IllegalStateException(msg + ": EGL error: 0x" + Integer.toHexString(error));
+ }
+ }
+
+ private void checkGlError(String msg) {
+ int error;
+ while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+ throw new IllegalStateException(msg + ": GLES20 error: 0x" + Integer.toHexString(error));
+ }
+ }
+
+ /**
+ * Return the surface texture to draw to - this is the texture use to when producing output
+ * surface buffers.
+ *
+ * @return a {@link SurfaceTexture}.
+ */
+ public SurfaceTexture getSurfaceTexture() {
+ return mSurfaceTexture;
+ }
+
+ /**
+ * Set a collection of output {@link Surface}s that can be drawn to.
+ *
+ * @param surfaces a {@link Collection} of surfaces.
+ */
+ public void configureSurfaces(Collection<Surface> surfaces) {
+ releaseEGLContext();
+
+ for (Surface s : surfaces) {
+ // If pixel conversions aren't handled by egl, use a pbuffer
+ if (LegacyCameraDevice.needsConversion(s)) {
+ LegacyCameraDevice.nativeSetSurfaceFormat(s, ImageFormat.NV21);
+ EGLSurfaceHolder holder = new EGLSurfaceHolder();
+ holder.surface = s;
+ mConversionSurfaces.add(holder);
+ } else {
+ EGLSurfaceHolder holder = new EGLSurfaceHolder();
+ holder.surface = s;
+ mSurfaces.add(holder);
+ }
+ }
+
+ // Set up egl display
+ configureEGLContext();
+
+ // Set up regular egl surfaces if needed
+ if (mSurfaces.size() > 0) {
+ configureEGLOutputSurfaces(mSurfaces);
+ }
+
+ // Set up pbuffer surface if needed
+ if (mConversionSurfaces.size() > 0) {
+ configureEGLPbufferSurfaces(mConversionSurfaces);
+ }
+ makeCurrent((mSurfaces.size() > 0) ? mSurfaces.get(0).eglSurface :
+ mConversionSurfaces.get(0).eglSurface);
+ initializeGLState();
+ mSurfaceTexture = new SurfaceTexture(getTextureId());
+ }
+
+ /**
+ * Draw the current buffer in the {@link SurfaceTexture} returned from
+ * {@link #getSurfaceTexture()} into the given set of target surfaces.
+ *
+ * <p>
+ * The given surfaces must be a subset of the surfaces set in the last
+ * {@link #configureSurfaces(java.util.Collection)} call.
+ * </p>
+ *
+ * @param targetSurfaces the surfaces to draw to.
+ */
+ public void drawIntoSurfaces(Collection<Surface> targetSurfaces) {
+ if ((mSurfaces == null || mSurfaces.size() == 0)
+ && (mConversionSurfaces == null || mConversionSurfaces.size() == 0)) {
+ return;
+ }
+ checkGlError("before updateTexImage");
+ mSurfaceTexture.updateTexImage();
+ for (EGLSurfaceHolder holder : mSurfaces) {
+ if (targetSurfaces.contains(holder.surface)) {
+ makeCurrent(holder.eglSurface);
+ drawFrame(mSurfaceTexture);
+ swapBuffers(holder.eglSurface);
+ }
+
+ }
+ for (EGLSurfaceHolder holder : mConversionSurfaces) {
+ if (targetSurfaces.contains(holder.surface)) {
+ makeCurrent(holder.eglSurface);
+ drawFrame(mSurfaceTexture);
+ mPBufferPixels.clear();
+ GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height, GLES20.GL_RGBA,
+ GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
+ checkGlError("glReadPixels");
+ int format = LegacyCameraDevice.nativeDetectSurfaceType(holder.surface);
+ LegacyCameraDevice.nativeProduceFrame(holder.surface, mPBufferPixels.array(),
+ holder.width, holder.height, format);
+ swapBuffers(holder.eglSurface);
+ }
+ }
+ }
+
+ /**
+ * Clean up the current GL context.
+ */
+ public void cleanupEGLContext() {
+ releaseEGLContext();
+ }
+
+ /**
+ * Drop all current GL operations on the floor.
+ */
+ public void flush() {
+ // TODO: implement flush
+ Log.e(TAG, "Flush not yet implemented.");
+ }
+}
diff --git a/core/java/android/hardware/camera2/legacy/package.html b/core/java/android/hardware/camera2/legacy/package.html
new file mode 100644
index 0000000..db6f78b
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/package.html
@@ -0,0 +1,3 @@
+<body>
+{@hide}
+</body> \ No newline at end of file
diff --git a/core/java/android/hardware/camera2/marshal/MarshalHelpers.java b/core/java/android/hardware/camera2/marshal/MarshalHelpers.java
new file mode 100644
index 0000000..35ecc2a
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/MarshalHelpers.java
@@ -0,0 +1,243 @@
+/*
+ * 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.marshal;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static com.android.internal.util.Preconditions.*;
+
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.util.Rational;
+
+/**
+ * Static functions in order to help implementing various marshaler functionality.
+ *
+ * <p>The intention is to statically import everything from this file into another file when
+ * implementing a new marshaler (or marshal queryable).</p>
+ *
+ * <p>The helpers are centered around providing primitive knowledge of the native types,
+ * such as the native size, the managed class wrappers, and various precondition checks.</p>
+ */
+public final class MarshalHelpers {
+
+ public static final int SIZEOF_BYTE = 1;
+ public static final int SIZEOF_INT32 = Integer.SIZE / Byte.SIZE;
+ public static final int SIZEOF_INT64 = Long.SIZE / Byte.SIZE;
+ public static final int SIZEOF_FLOAT = Float.SIZE / Byte.SIZE;
+ public static final int SIZEOF_DOUBLE = Double.SIZE / Byte.SIZE;
+ public static final int SIZEOF_RATIONAL = SIZEOF_INT32 * 2;
+
+ /**
+ * Get the size in bytes for the native camera metadata type.
+ *
+ * <p>This used to determine how many bytes it would take to encode/decode a single value
+ * of that {@link nativeType}.</p>
+ *
+ * @param nativeType the native type, e.g.
+ * {@link android.hardware.camera2.impl.CameraMetadataNative#TYPE_BYTE TYPE_BYTE}.
+ * @return size in bytes >= 1
+ *
+ * @throws UnsupportedOperationException if nativeType was not one of the built-in types
+ */
+ public static int getPrimitiveTypeSize(int nativeType) {
+ switch (nativeType) {
+ case TYPE_BYTE:
+ return SIZEOF_BYTE;
+ case TYPE_INT32:
+ return SIZEOF_INT32;
+ case TYPE_FLOAT:
+ return SIZEOF_FLOAT;
+ case TYPE_INT64:
+ return SIZEOF_INT64;
+ case TYPE_DOUBLE:
+ return SIZEOF_DOUBLE;
+ case TYPE_RATIONAL:
+ return SIZEOF_RATIONAL;
+ }
+
+ throw new UnsupportedOperationException("Unknown type, can't get size for "
+ + nativeType);
+ }
+
+
+ /**
+ * Ensure that the {@code klass} is one of the metadata-primitive classes.
+ *
+ * @param klass a non-{@code null} reference
+ * @return {@code klass} instance
+ *
+ * @throws UnsupportedOperationException if klass was not one of the built-in classes
+ * @throws NullPointerException if klass was null
+ *
+ * @see #isPrimitiveClass
+ */
+ public static <T> Class<T> checkPrimitiveClass(Class<T> klass) {
+ checkNotNull(klass, "klass must not be null");
+
+ if (isPrimitiveClass(klass)) {
+ return klass;
+ }
+
+ throw new UnsupportedOperationException("Unsupported class '" + klass +
+ "'; expected a metadata primitive class");
+ }
+
+ /**
+ * Checks whether or not {@code klass} is one of the metadata-primitive classes.
+ *
+ * <p>The following types (whether boxed or unboxed) are considered primitive:
+ * <ul>
+ * <li>byte
+ * <li>int
+ * <li>float
+ * <li>double
+ * <li>Rational
+ * </ul>
+ * </p>
+ *
+ * <p>This doesn't strictly follow the java understanding of primitive since
+ * boxed objects are included, Rational is included, and other types such as char and
+ * short are not included.</p>
+ *
+ * @param klass a {@link Class} instance; using {@code null} will return {@code false}
+ * @return {@code true} if primitive, {@code false} otherwise
+ */
+ public static <T> boolean isPrimitiveClass(Class<T> klass) {
+ if (klass == null) {
+ return false;
+ }
+
+ if (klass == byte.class || klass == Byte.class) {
+ return true;
+ } else if (klass == int.class || klass == Integer.class) {
+ return true;
+ } else if (klass == float.class || klass == Float.class) {
+ return true;
+ } else if (klass == long.class || klass == Long.class) {
+ return true;
+ } else if (klass == double.class || klass == Double.class) {
+ return true;
+ } else if (klass == Rational.class) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Wrap {@code klass} with its wrapper variant if it was a {@code Class} corresponding
+ * to a Java primitive.
+ *
+ * <p>Non-primitive classes are passed through as-is.</p>
+ *
+ * <p>For example, for a primitive {@code int.class => Integer.class},
+ * but for a non-primitive {@code Rational.class => Rational.class}.</p>
+ *
+ * @param klass a {@code Class} reference
+ *
+ * @return wrapped class object, or same class object if non-primitive
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Class<T> wrapClassIfPrimitive(Class<T> klass) {
+ if (klass == byte.class) {
+ return (Class<T>)Byte.class;
+ } else if (klass == int.class) {
+ return (Class<T>)Integer.class;
+ } else if (klass == float.class) {
+ return (Class<T>)Float.class;
+ } else if (klass == long.class) {
+ return (Class<T>)Long.class;
+ } else if (klass == double.class) {
+ return (Class<T>)Double.class;
+ }
+
+ return klass;
+ }
+
+ /**
+ * Return a human-readable representation of the {@code nativeType}, e.g. "TYPE_INT32"
+ *
+ * <p>Out-of-range values return a string with "UNKNOWN" as the prefix.</p>
+ *
+ * @param nativeType the native type
+ *
+ * @return human readable type name
+ */
+ public static String toStringNativeType(int nativeType) {
+ switch (nativeType) {
+ case TYPE_BYTE:
+ return "TYPE_BYTE";
+ case TYPE_INT32:
+ return "TYPE_INT32";
+ case TYPE_FLOAT:
+ return "TYPE_FLOAT";
+ case TYPE_INT64:
+ return "TYPE_INT64";
+ case TYPE_DOUBLE:
+ return "TYPE_DOUBLE";
+ case TYPE_RATIONAL:
+ return "TYPE_RATIONAL";
+ }
+
+ return "UNKNOWN(" + nativeType + ")";
+ }
+
+ /**
+ * Ensure that the {@code nativeType} is one of the native types supported
+ * by {@link CameraMetadataNative}.
+ *
+ * @param nativeType the native type
+ *
+ * @return the native type
+ *
+ * @throws UnsupportedOperationException if the native type was invalid
+ */
+ public static int checkNativeType(int nativeType) {
+ switch (nativeType) {
+ case TYPE_BYTE:
+ case TYPE_INT32:
+ case TYPE_FLOAT:
+ case TYPE_INT64:
+ case TYPE_DOUBLE:
+ case TYPE_RATIONAL:
+ return nativeType;
+ }
+
+ throw new UnsupportedOperationException("Unknown nativeType " + nativeType);
+ }
+
+ /**
+ * Ensure that the expected and actual native types are equal.
+ *
+ * @param expectedNativeType the expected native type
+ * @param actualNativeType the actual native type
+ * @return the actual native type
+ *
+ * @throws UnsupportedOperationException if the types are not equal
+ */
+ public static int checkNativeTypeEquals(int expectedNativeType, int actualNativeType) {
+ if (expectedNativeType != actualNativeType) {
+ throw new UnsupportedOperationException(
+ String.format("Expected native type %d, but got %d",
+ expectedNativeType, actualNativeType));
+ }
+
+ return actualNativeType;
+ }
+
+ private MarshalHelpers() {
+ throw new AssertionError();
+ }
+}
diff --git a/core/java/android/hardware/camera2/marshal/MarshalQueryable.java b/core/java/android/hardware/camera2/marshal/MarshalQueryable.java
new file mode 100644
index 0000000..35fed1f
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/MarshalQueryable.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 android.hardware.camera2.marshal;
+
+import android.hardware.camera2.utils.TypeReference;
+
+/**
+ * Query if a marshaler can marshal to/from a particular native and managed type; if it supports
+ * the combination, allow creating a marshaler instance to do the serialization.
+ *
+ * <p>Not all queryable instances will support exactly one combination. Some, such as the
+ * primitive queryable will support all primitive to/from managed mappings (as long as they are
+ * 1:1). Others, such as the rectangle queryable will only support integer to rectangle mappings.
+ * </p>
+ *
+ * <p>Yet some others are codependent on other queryables; e.g. array queryables might only support
+ * a type map for {@code T[]} if another queryable exists with support for the component type
+ * {@code T}.</p>
+ */
+public interface MarshalQueryable<T> {
+ /**
+ * Create a marshaler between the selected managed and native type.
+ *
+ * <p>This marshaler instance is only good for that specific type mapping; and will refuse
+ * to map other managed types, other native types, or an other combination that isn't
+ * this exact one.</p>
+ *
+ * @param managedType a managed type reference
+ * @param nativeType the native type, e.g.
+ * {@link android.hardware.camera2.impl.CameraMetadataNative#TYPE_BYTE TYPE_BYTE}
+ * @return
+ *
+ * @throws UnsupportedOperationException
+ * if {@link #isTypeMappingSupported} returns {@code false}
+ */
+ public Marshaler<T> createMarshaler(
+ TypeReference<T> managedType, int nativeType);
+
+ /**
+ * Determine whether or not this query marshal is able to create a marshaler that will
+ * support the managed type and native type mapping.
+ *
+ * <p>If this returns {@code true}, then a marshaler can be instantiated by
+ * {@link #createMarshaler} that will marshal data to/from the native type
+ * from/to the managed type.</p>
+ *
+ * <p>Most marshalers are likely to only support one type map.</p>
+ */
+ public boolean isTypeMappingSupported(TypeReference<T> managedType, int nativeType);
+}
diff --git a/core/java/android/hardware/camera2/marshal/MarshalRegistry.java b/core/java/android/hardware/camera2/marshal/MarshalRegistry.java
new file mode 100644
index 0000000..92d9057
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/MarshalRegistry.java
@@ -0,0 +1,133 @@
+/*
+ * 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.marshal;
+
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.utils.TypeReference;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Registry of supported marshalers; add new query-able marshalers or lookup existing ones.</p>
+ */
+public class MarshalRegistry {
+
+ /**
+ * Register a marshal queryable for the managed type {@code T}.
+ *
+ * <p>Multiple marshal queryables for the same managed type {@code T} may be registered;
+ * this is desirable if they support different native types (e.g. marshaler 1 supports
+ * {@code Integer <-> TYPE_INT32}, marshaler 2 supports {@code Integer <-> TYPE_BYTE}.</p>
+ *
+ * @param queryable a non-{@code null} marshal queryable that supports marshaling {@code T}
+ */
+ public static <T> void registerMarshalQueryable(MarshalQueryable<T> queryable) {
+ sRegisteredMarshalQueryables.add(queryable);
+ }
+
+ /**
+ * Lookup a marshaler between {@code T} and {@code nativeType}.
+ *
+ * <p>Marshalers are looked up in the order they were registered; earlier registered
+ * marshal queriers get priority.</p>
+ *
+ * @param typeToken The compile-time type reference for {@code T}
+ * @param nativeType The native type, e.g. {@link CameraMetadataNative#TYPE_BYTE TYPE_BYTE}
+ * @return marshaler a non-{@code null} marshaler that supports marshaling the type combo
+ *
+ * @throws UnsupportedOperationException If no marshaler matching the args could be found
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Marshaler<T> getMarshaler(TypeReference<T> typeToken, int nativeType) {
+ // TODO: can avoid making a new token each time by code-genning
+ // the list of type tokens and native types from the keys (at the call sites)
+ MarshalToken<T> marshalToken = new MarshalToken<T>(typeToken, nativeType);
+
+ /*
+ * Marshalers are instantiated lazily once they are looked up; successive lookups
+ * will not instantiate new marshalers.
+ */
+ Marshaler<T> marshaler =
+ (Marshaler<T>) sMarshalerMap.get(marshalToken);
+
+ if (sRegisteredMarshalQueryables.size() == 0) {
+ throw new AssertionError("No available query marshalers registered");
+ }
+
+ if (marshaler == null) {
+ // Query each marshaler to see if they support the native/managed type combination
+ for (MarshalQueryable<?> potentialMarshaler : sRegisteredMarshalQueryables) {
+
+ MarshalQueryable<T> castedPotential =
+ (MarshalQueryable<T>)potentialMarshaler;
+
+ if (castedPotential.isTypeMappingSupported(typeToken, nativeType)) {
+ marshaler = castedPotential.createMarshaler(typeToken, nativeType);
+ break;
+ }
+ }
+ }
+
+ if (marshaler == null) {
+ throw new UnsupportedOperationException(
+ "Could not find marshaler that matches the requested " +
+ "combination of type reference " +
+ typeToken + " and native type " +
+ MarshalHelpers.toStringNativeType(nativeType));
+ }
+
+ sMarshalerMap.put(marshalToken, marshaler);
+
+ return marshaler;
+ }
+
+ private static class MarshalToken<T> {
+ public MarshalToken(TypeReference<T> typeReference, int nativeType) {
+ this.typeReference = typeReference;
+ this.nativeType = nativeType;
+ }
+
+ final TypeReference<T> typeReference;
+ final int nativeType;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof MarshalToken<?>) {
+ MarshalToken<?> otherToken = (MarshalToken<?>)other;
+ return typeReference.equals(otherToken.typeReference) &&
+ nativeType == otherToken.nativeType;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return typeReference.hashCode() ^ nativeType;
+ }
+ }
+
+ private static List<MarshalQueryable<?>> sRegisteredMarshalQueryables =
+ new ArrayList<MarshalQueryable<?>>();
+ private static HashMap<MarshalToken<?>, Marshaler<?>> sMarshalerMap =
+ new HashMap<MarshalToken<?>, Marshaler<?>>();
+
+ private MarshalRegistry() {
+ throw new AssertionError();
+ }
+}
diff --git a/core/java/android/hardware/camera2/marshal/Marshaler.java b/core/java/android/hardware/camera2/marshal/Marshaler.java
new file mode 100644
index 0000000..eb0ad15
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/Marshaler.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.hardware.camera2.marshal;
+
+import android.hardware.camera2.utils.TypeReference;
+
+import java.nio.ByteBuffer;
+
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+import static com.android.internal.util.Preconditions.*;
+
+/**
+ * Base class to marshal data to/from managed/native metadata byte buffers.
+ *
+ * <p>This class should not be created directly; an instance of it can be obtained
+ * using {@link MarshalQueryable#createMarshaler} for the same type {@code T} if the native type
+ * mapping for {@code T} {@link MarshalQueryable#isTypeMappingSupported supported}.</p>
+ *
+ * @param <T> the compile-time managed type
+ */
+public abstract class Marshaler<T> {
+
+ protected final TypeReference<T> mTypeReference;
+ protected final int mNativeType;
+
+ /**
+ * Instantiate a marshaler between a single managed/native type combination.
+ *
+ * <p>This particular managed/native type combination must be supported by
+ * {@link #isTypeMappingSupported}.</p>
+ *
+ * @param query an instance of {@link MarshalQueryable}
+ * @param typeReference the managed type reference
+ * Must be one for which {@link #isTypeMappingSupported} returns {@code true}
+ * @param nativeType the native type, e.g.
+ * {@link android.hardware.camera2.impl.CameraMetadataNative#TYPE_BYTE TYPE_BYTE}.
+ * Must be one for which {@link #isTypeMappingSupported} returns {@code true}.
+ *
+ * @throws NullPointerException if any args were {@code null}
+ * @throws UnsupportedOperationException if the type mapping was not supported
+ */
+ protected Marshaler(
+ MarshalQueryable<T> query, TypeReference<T> typeReference, int nativeType) {
+ mTypeReference = checkNotNull(typeReference, "typeReference must not be null");
+ mNativeType = checkNativeType(nativeType);
+
+ if (!query.isTypeMappingSupported(typeReference, nativeType)) {
+ throw new UnsupportedOperationException(
+ "Unsupported type marshaling for managed type "
+ + typeReference + " and native type "
+ + MarshalHelpers.toStringNativeType(nativeType));
+ }
+ }
+
+ /**
+ * Marshal the specified object instance (value) into a byte buffer.
+ *
+ * <p>Upon completion, the {@link ByteBuffer#position()} will have advanced by
+ * the {@link #calculateMarshalSize marshal size} of {@code value}.</p>
+ *
+ * @param value the value of type T that we wish to write into the byte buffer
+ * @param buffer the byte buffer into which the marshaled object will be written
+ */
+ public abstract void marshal(T value, ByteBuffer buffer);
+
+ /**
+ * Get the size in bytes for how much space would be required to write this {@code value}
+ * into a byte buffer using the given {@code nativeType}.
+ *
+ * <p>If the size of this {@code T} instance when serialized into a buffer is always constant,
+ * then this method will always return the same value (and particularly, it will return
+ * an equivalent value to {@link #getNativeSize()}.</p>
+ *
+ * <p>Overriding this method is a must when the size is {@link NATIVE_SIZE_DYNAMIC dynamic}.</p>
+ *
+ * @param value the value of type T that we wish to write into the byte buffer
+ * @return the size that would need to be written to the byte buffer
+ */
+ public int calculateMarshalSize(T value) {
+ int nativeSize = getNativeSize();
+
+ if (nativeSize == NATIVE_SIZE_DYNAMIC) {
+ throw new AssertionError("Override this function for dynamically-sized objects");
+ }
+
+ return nativeSize;
+ }
+
+ /**
+ * Unmarshal a new object instance from the byte buffer into its managed type.
+ *
+ * <p>Upon completion, the {@link ByteBuffer#position()} will have advanced by
+ * the {@link #calculateMarshalSize marshal size} of the returned {@code T} instance.</p>
+ *
+ * @param buffer the byte buffer, from which we will read the object
+ * @return a new instance of type T read from the byte buffer
+ */
+ public abstract T unmarshal(ByteBuffer buffer);
+
+ /**
+ * Used to denote variable-length data structures.
+ *
+ * <p>If the size is dynamic then we can't know ahead of time how big of a data structure
+ * to preallocate for e.g. arrays, so one object must be unmarshaled at a time.</p>
+ */
+ public static int NATIVE_SIZE_DYNAMIC = -1;
+
+ /**
+ * How many bytes a single instance of {@code T} will take up if marshalled to/from
+ * {@code nativeType}.
+ *
+ * <p>When unmarshaling data from native to managed, the instance {@code T} is not yet
+ * available. If the native size is always a fixed mapping regardless of the instance of
+ * {@code T} (e.g. if the type is not a container of some sort), it can be used to preallocate
+ * containers for {@code T} to avoid resizing them.</p>
+ *
+ * <p>In particular, the array marshaler takes advantage of this (when size is not dynamic)
+ * to preallocate arrays of the right length when unmarshaling an array {@code T[]}.</p>
+ *
+ * @return a size in bytes, or {@link #NATIVE_SIZE_DYNAMIC} if the size is dynamic
+ */
+ public abstract int getNativeSize();
+
+ /**
+ * The type reference for {@code T} for the managed type side of this marshaler.
+ */
+ public TypeReference<T> getTypeReference() {
+ return mTypeReference;
+ }
+
+ /** The native type corresponding to this marshaler for the native side of this marshaler.*/
+ public int getNativeType() {
+ return mNativeType;
+ }
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java
new file mode 100644
index 0000000..22b87ef
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java
@@ -0,0 +1,182 @@
+/*
+ * 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.marshal.impl;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.marshal.MarshalRegistry;
+import android.hardware.camera2.utils.TypeReference;
+import android.util.Log;
+
+import java.lang.reflect.Array;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+
+/**
+ * Marshal any array {@code T}.
+ *
+ * <p>To marshal any {@code T} to/from a native type, the marshaler for T to/from that native type
+ * also has to exist.</p>
+ *
+ * <p>{@code T} can be either a T2[] where T2 is an object type, or a P[] where P is a
+ * built-in primitive (e.g. int[], float[], etc).</p>
+
+ * @param <T> the type of the array (e.g. T = int[], or T = Rational[])
+ */
+public class MarshalQueryableArray<T> implements MarshalQueryable<T> {
+
+ private static final String TAG = MarshalQueryableArray.class.getSimpleName();
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private class MarshalerArray extends Marshaler<T> {
+ private final Class<T> mClass;
+ private final Marshaler<?> mComponentMarshaler;
+ private final Class<?> mComponentClass;
+
+ @SuppressWarnings("unchecked")
+ protected MarshalerArray(TypeReference<T> typeReference, int nativeType) {
+ super(MarshalQueryableArray.this, typeReference, nativeType);
+
+ mClass = (Class<T>)typeReference.getRawType();
+
+ TypeReference<?> componentToken = typeReference.getComponentType();
+ mComponentMarshaler = MarshalRegistry.getMarshaler(componentToken, mNativeType);
+ mComponentClass = componentToken.getRawType();
+ }
+
+ @Override
+ public void marshal(T value, ByteBuffer buffer) {
+ int length = Array.getLength(value);
+ for (int i = 0; i < length; ++i) {
+ marshalArrayElement(mComponentMarshaler, buffer, value, i);
+ }
+ }
+
+ @Override
+ public T unmarshal(ByteBuffer buffer) {
+ Object array;
+
+ int elementSize = mComponentMarshaler.getNativeSize();
+
+ if (elementSize != Marshaler.NATIVE_SIZE_DYNAMIC) {
+ int remaining = buffer.remaining();
+ int arraySize = remaining / elementSize;
+
+ if (remaining % elementSize != 0) {
+ throw new UnsupportedOperationException("Arrays for " + mTypeReference
+ + " must be packed tighly into a multiple of " + elementSize
+ + "; but there are " + (remaining % elementSize) + " left over bytes");
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG, String.format(
+ "Attempting to unpack array (count = %d, element size = %d, bytes "
+ + "remaining = %d) for type %s",
+ arraySize, elementSize, remaining, mClass));
+ }
+
+ array = Array.newInstance(mComponentClass, arraySize);
+ for (int i = 0; i < arraySize; ++i) {
+ Object elem = mComponentMarshaler.unmarshal(buffer);
+ Array.set(array, i, elem);
+ }
+ } else {
+ // Dynamic size, use an array list.
+ ArrayList<Object> arrayList = new ArrayList<Object>();
+
+ // Assumes array is packed tightly; no unused bytes allowed
+ while (buffer.hasRemaining()) {
+ Object elem = mComponentMarshaler.unmarshal(buffer);
+ arrayList.add(elem);
+ }
+
+ int arraySize = arrayList.size();
+ array = copyListToArray(arrayList, Array.newInstance(mComponentClass, arraySize));
+ }
+
+ if (buffer.remaining() != 0) {
+ Log.e(TAG, "Trailing bytes (" + buffer.remaining() + ") left over after unpacking "
+ + mClass);
+ }
+
+ return mClass.cast(array);
+ }
+
+ @Override
+ public int getNativeSize() {
+ return NATIVE_SIZE_DYNAMIC;
+ }
+
+ @Override
+ public int calculateMarshalSize(T value) {
+ int elementSize = mComponentMarshaler.getNativeSize();
+ int arrayLength = Array.getLength(value);
+
+ if (elementSize != Marshaler.NATIVE_SIZE_DYNAMIC) {
+ // The fast way. Every element size is uniform.
+ return elementSize * arrayLength;
+ } else {
+ // The slow way. Accumulate size for each element.
+ int size = 0;
+ for (int i = 0; i < arrayLength; ++i) {
+ size += calculateElementMarshalSize(mComponentMarshaler, value, i);
+ }
+
+ return size;
+ }
+ }
+
+ /*
+ * Helpers to avoid compiler errors regarding types with wildcards (?)
+ */
+
+ @SuppressWarnings("unchecked")
+ private <TElem> void marshalArrayElement(Marshaler<TElem> marshaler,
+ ByteBuffer buffer, Object array, int index) {
+ marshaler.marshal((TElem)Array.get(array, index), buffer);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Object copyListToArray(ArrayList<?> arrayList, Object arrayDest) {
+ return arrayList.toArray((T[]) arrayDest);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <TElem> int calculateElementMarshalSize(Marshaler<TElem> marshaler,
+ Object array, int index) {
+ Object elem = Array.get(array, index);
+
+ return marshaler.calculateMarshalSize((TElem) elem);
+ }
+ }
+
+ @Override
+ public Marshaler<T> createMarshaler(TypeReference<T> managedType, int nativeType) {
+ return new MarshalerArray(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<T> managedType, int nativeType) {
+ // support both ConcreteType[] and GenericType<ConcreteType>[]
+ return managedType.getRawType().isArray();
+
+ // TODO: Should this recurse deeper and check that there is
+ // a valid marshaler for the ConcreteType as well?
+ }
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableBoolean.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableBoolean.java
new file mode 100644
index 0000000..4aa4b4a
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableBoolean.java
@@ -0,0 +1,67 @@
+/*
+ * 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.marshal.impl;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.utils.TypeReference;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Marshal booleans: TYPE_BYTE <-> boolean/Boolean
+ */
+public class MarshalQueryableBoolean implements MarshalQueryable<Boolean> {
+
+ private class MarshalerBoolean extends Marshaler<Boolean> {
+ protected MarshalerBoolean(TypeReference<Boolean> typeReference, int nativeType) {
+ super(MarshalQueryableBoolean.this, typeReference, nativeType);
+ }
+
+ @Override
+ public void marshal(Boolean value, ByteBuffer buffer) {
+ boolean unboxValue = value;
+ buffer.put((byte)(unboxValue ? 1 : 0));
+ }
+
+ @Override
+ public Boolean unmarshal(ByteBuffer buffer) {
+ return buffer.get() != 0;
+ }
+
+ @Override
+ public int getNativeSize() {
+ return SIZEOF_BYTE;
+ }
+ }
+
+ @Override
+ public Marshaler<Boolean> createMarshaler(TypeReference<Boolean> managedType,
+ int nativeType) {
+ return new MarshalerBoolean(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<Boolean> managedType, int nativeType) {
+ return (Boolean.class.equals(managedType.getType())
+ || boolean.class.equals(managedType.getType())) && nativeType == TYPE_BYTE;
+ }
+
+
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableColorSpaceTransform.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableColorSpaceTransform.java
new file mode 100644
index 0000000..47f79bf
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableColorSpaceTransform.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 android.hardware.camera2.marshal.impl;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.params.ColorSpaceTransform;
+import android.hardware.camera2.utils.TypeReference;
+
+import java.nio.ByteBuffer;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+
+/**
+ * Marshal {@link ColorSpaceTransform} to/from {@link #TYPE_RATIONAL}
+ */
+public class MarshalQueryableColorSpaceTransform implements
+ MarshalQueryable<ColorSpaceTransform> {
+
+ private static final int ELEMENTS_INT32 = 3 * 3 * (SIZEOF_RATIONAL / SIZEOF_INT32);
+ private static final int SIZE = SIZEOF_INT32 * ELEMENTS_INT32;
+
+ /** rational x 3 x 3 */
+ private class MarshalerColorSpaceTransform extends Marshaler<ColorSpaceTransform> {
+ protected MarshalerColorSpaceTransform(TypeReference<ColorSpaceTransform> typeReference,
+ int nativeType) {
+ super(MarshalQueryableColorSpaceTransform.this, typeReference, nativeType);
+ }
+
+ @Override
+ public void marshal(ColorSpaceTransform value, ByteBuffer buffer) {
+ int[] transformAsArray = new int[ELEMENTS_INT32];
+ value.copyElements(transformAsArray, /*offset*/0);
+
+ for (int i = 0; i < ELEMENTS_INT32; ++i) {
+ buffer.putInt(transformAsArray[i]);
+ }
+ }
+
+ @Override
+ public ColorSpaceTransform unmarshal(ByteBuffer buffer) {
+ int[] transformAsArray = new int[ELEMENTS_INT32];
+
+ for (int i = 0; i < ELEMENTS_INT32; ++i) {
+ transformAsArray[i] = buffer.getInt();
+ }
+
+ return new ColorSpaceTransform(transformAsArray);
+ }
+
+ @Override
+ public int getNativeSize() {
+ return SIZE;
+ }
+ }
+
+ @Override
+ public Marshaler<ColorSpaceTransform> createMarshaler(
+ TypeReference<ColorSpaceTransform> managedType, int nativeType) {
+ return new MarshalerColorSpaceTransform(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(
+ TypeReference<ColorSpaceTransform> managedType, int nativeType) {
+ return nativeType == TYPE_RATIONAL &&
+ ColorSpaceTransform.class.equals(managedType.getType());
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java
new file mode 100644
index 0000000..fa53db2
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java
@@ -0,0 +1,220 @@
+/*
+ * 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.marshal.impl;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.utils.TypeReference;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+
+/**
+ * Marshal any simple enum (0-arg constructors only) into/from either
+ * {@code TYPE_BYTE} or {@code TYPE_INT32}.
+ *
+ * <p>Default values of the enum are mapped to its ordinal; this can be overridden
+ * by providing a manual value with {@link #registerEnumValues}.</p>
+
+ * @param <T> the type of {@code Enum}
+ */
+public class MarshalQueryableEnum<T extends Enum<T>> implements MarshalQueryable<T> {
+
+ private static final String TAG = MarshalQueryableEnum.class.getSimpleName();
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static final int UINT8_MIN = 0x0;
+ private static final int UINT8_MAX = (1 << Byte.SIZE) - 1;
+ private static final int UINT8_MASK = UINT8_MAX;
+
+ private class MarshalerEnum extends Marshaler<T> {
+
+ private final Class<T> mClass;
+
+ @SuppressWarnings("unchecked")
+ protected MarshalerEnum(TypeReference<T> typeReference, int nativeType) {
+ super(MarshalQueryableEnum.this, typeReference, nativeType);
+
+ mClass = (Class<T>)typeReference.getRawType();
+ }
+
+ @Override
+ public void marshal(T value, ByteBuffer buffer) {
+ int enumValue = getEnumValue(value);
+
+ if (mNativeType == TYPE_INT32) {
+ buffer.putInt(enumValue);
+ } else if (mNativeType == TYPE_BYTE) {
+ if (enumValue < UINT8_MIN || enumValue > UINT8_MAX) {
+ throw new UnsupportedOperationException(String.format(
+ "Enum value %x too large to fit into unsigned byte", enumValue));
+ }
+ buffer.put((byte)enumValue);
+ } else {
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ public T unmarshal(ByteBuffer buffer) {
+ int enumValue;
+
+ switch (mNativeType) {
+ case TYPE_INT32:
+ enumValue = buffer.getInt();
+ break;
+ case TYPE_BYTE:
+ // get the unsigned byte value; avoid sign extension
+ enumValue = buffer.get() & UINT8_MASK;
+ break;
+ default:
+ throw new AssertionError(
+ "Unexpected native type; impossible since its not supported");
+ }
+
+ return getEnumFromValue(mClass, enumValue);
+ }
+
+ @Override
+ public int getNativeSize() {
+ return getPrimitiveTypeSize(mNativeType);
+ }
+ }
+
+ @Override
+ public Marshaler<T> createMarshaler(TypeReference<T> managedType, int nativeType) {
+ return new MarshalerEnum(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<T> managedType, int nativeType) {
+ if (nativeType == TYPE_INT32 || nativeType == TYPE_BYTE) {
+ if (managedType.getType() instanceof Class<?>) {
+ Class<?> typeClass = (Class<?>)managedType.getType();
+
+ if (typeClass.isEnum()) {
+ if (VERBOSE) {
+ Log.v(TAG, "possible enum detected for " + typeClass);
+ }
+
+ // The enum must not take extra arguments
+ try {
+ // match a class like: "public enum Fruits { Apple, Orange; }"
+ typeClass.getDeclaredConstructor(String.class, int.class);
+ return true;
+ } catch (NoSuchMethodException e) {
+ // Skip: custom enum with a special constructor e.g. Foo(T), but need Foo()
+ Log.e(TAG, "Can't marshal class " + typeClass + "; no default constructor");
+ } catch (SecurityException e) {
+ // Skip: wouldn't be able to touch the enum anyway
+ Log.e(TAG, "Can't marshal class " + typeClass + "; not accessible");
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static final HashMap<Class<? extends Enum>, int[]> sEnumValues =
+ new HashMap<Class<? extends Enum>, int[]>();
+
+ /**
+ * Register a non-sequential set of values to be used with the marshal/unmarshal functions.
+ *
+ * <p>This enables get/set to correctly marshal the enum into a value that is C-compatible.</p>
+ *
+ * @param enumType The class for an enum
+ * @param values A list of values mapping to the ordinals of the enum
+ */
+ public static <T extends Enum<T>> void registerEnumValues(Class<T> enumType, int[] values) {
+ if (enumType.getEnumConstants().length != values.length) {
+ throw new IllegalArgumentException(
+ "Expected values array to be the same size as the enumTypes values "
+ + values.length + " for type " + enumType);
+ }
+ if (VERBOSE) {
+ Log.v(TAG, "Registered enum values for type " + enumType + " values");
+ }
+
+ sEnumValues.put(enumType, values);
+ }
+
+ /**
+ * Get the numeric value from an enum.
+ *
+ * <p>This is usually the same as the ordinal value for
+ * enums that have fully sequential values, although for C-style enums the range of values
+ * may not map 1:1.</p>
+ *
+ * @param enumValue Enum instance
+ * @return Int guaranteed to be ABI-compatible with the C enum equivalent
+ */
+ private static <T extends Enum<T>> int getEnumValue(T enumValue) {
+ int[] values;
+ values = sEnumValues.get(enumValue.getClass());
+
+ int ordinal = enumValue.ordinal();
+ if (values != null) {
+ return values[ordinal];
+ }
+
+ return ordinal;
+ }
+
+ /**
+ * Finds the enum corresponding to it's numeric value. Opposite of {@link #getEnumValue} method.
+ *
+ * @param enumType Class of the enum we want to find
+ * @param value The numeric value of the enum
+ * @return An instance of the enum
+ */
+ private static <T extends Enum<T>> T getEnumFromValue(Class<T> enumType, int value) {
+ int ordinal;
+
+ int[] registeredValues = sEnumValues.get(enumType);
+ if (registeredValues != null) {
+ ordinal = -1;
+
+ for (int i = 0; i < registeredValues.length; ++i) {
+ if (registeredValues[i] == value) {
+ ordinal = i;
+ break;
+ }
+ }
+ } else {
+ ordinal = value;
+ }
+
+ T[] values = enumType.getEnumConstants();
+
+ if (ordinal < 0 || ordinal >= values.length) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Argument 'value' (%d) was not a valid enum value for type %s "
+ + "(registered? %b)",
+ value,
+ enumType, (registeredValues != null)));
+ }
+
+ return values[ordinal];
+ }
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableMeteringRectangle.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableMeteringRectangle.java
new file mode 100644
index 0000000..01780db
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableMeteringRectangle.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.hardware.camera2.marshal.impl;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.utils.TypeReference;
+
+import java.nio.ByteBuffer;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+
+/**
+ * Marshal {@link MeteringRectangle} to/from {@link #TYPE_INT32}
+ */
+public class MarshalQueryableMeteringRectangle implements MarshalQueryable<MeteringRectangle> {
+ private static final int SIZE = SIZEOF_INT32 * 5;
+
+ /** (xmin, ymin, xmax, ymax, weight) */
+ private class MarshalerMeteringRectangle extends Marshaler<MeteringRectangle> {
+ protected MarshalerMeteringRectangle(TypeReference<MeteringRectangle> typeReference,
+ int nativeType) {
+ super(MarshalQueryableMeteringRectangle.this, typeReference, nativeType);
+ }
+
+ @Override
+ public void marshal(MeteringRectangle value, ByteBuffer buffer) {
+ int xMin = value.getX();
+ int yMin = value.getY();
+ int xMax = xMin + value.getWidth();
+ int yMax = yMin + value.getHeight();
+ int weight = value.getMeteringWeight();
+
+ buffer.putInt(xMin);
+ buffer.putInt(yMin);
+ buffer.putInt(xMax);
+ buffer.putInt(yMax);
+ buffer.putInt(weight);
+ }
+
+ @Override
+ public MeteringRectangle unmarshal(ByteBuffer buffer) {
+ int xMin = buffer.getInt();
+ int yMin = buffer.getInt();
+ int xMax = buffer.getInt();
+ int yMax = buffer.getInt();
+ int weight = buffer.getInt();
+
+ int width = xMax - xMin;
+ int height = yMax - yMin;
+
+ return new MeteringRectangle(xMin, yMin, width, height, weight);
+ }
+
+ @Override
+ public int getNativeSize() {
+ return SIZE;
+ }
+ }
+
+ @Override
+ public Marshaler<MeteringRectangle> createMarshaler(
+ TypeReference<MeteringRectangle> managedType, int nativeType) {
+ return new MarshalerMeteringRectangle(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(
+ TypeReference<MeteringRectangle> managedType, int nativeType) {
+ return nativeType == TYPE_INT32 && MeteringRectangle.class.equals(managedType.getType());
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableNativeByteToInteger.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableNativeByteToInteger.java
new file mode 100644
index 0000000..3b89c82
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableNativeByteToInteger.java
@@ -0,0 +1,70 @@
+/*
+ * 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.marshal.impl;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.utils.TypeReference;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Marshal fake native enums (ints): TYPE_BYTE <-> int/Integer
+ */
+public class MarshalQueryableNativeByteToInteger implements MarshalQueryable<Integer> {
+
+ private static final int UINT8_MASK = (1 << Byte.SIZE) - 1;
+
+ private class MarshalerNativeByteToInteger extends Marshaler<Integer> {
+ protected MarshalerNativeByteToInteger(TypeReference<Integer> typeReference,
+ int nativeType) {
+ super(MarshalQueryableNativeByteToInteger.this, typeReference, nativeType);
+ }
+
+ @Override
+ public void marshal(Integer value, ByteBuffer buffer) {
+ buffer.put((byte)(int)value); // truncate down to byte
+ }
+
+ @Override
+ public Integer unmarshal(ByteBuffer buffer) {
+ // expand unsigned byte to int; avoid sign extension
+ return buffer.get() & UINT8_MASK;
+ }
+
+ @Override
+ public int getNativeSize() {
+ return SIZEOF_BYTE;
+ }
+ }
+
+ @Override
+ public Marshaler<Integer> createMarshaler(TypeReference<Integer> managedType,
+ int nativeType) {
+ return new MarshalerNativeByteToInteger(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<Integer> managedType, int nativeType) {
+ return (Integer.class.equals(managedType.getType())
+ || int.class.equals(managedType.getType())) && nativeType == TYPE_BYTE;
+ }
+
+
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePair.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePair.java
new file mode 100644
index 0000000..0a9935d
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePair.java
@@ -0,0 +1,158 @@
+/*
+ * 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.marshal.impl;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.marshal.MarshalRegistry;
+import android.hardware.camera2.utils.TypeReference;
+import android.util.Pair;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.nio.ByteBuffer;
+
+/**
+ * Marshal {@link Pair} to/from any native type
+ */
+public class MarshalQueryablePair<T1, T2>
+ implements MarshalQueryable<Pair<T1, T2>> {
+
+ private class MarshalerPair extends Marshaler<Pair<T1, T2>> {
+ private final Class<? super Pair<T1, T2>> mClass;
+ private final Constructor<Pair<T1, T2>> mConstructor;
+ /** Marshal the {@code T1} inside of {@code Pair<T1, T2>} */
+ private final Marshaler<T1> mNestedTypeMarshalerFirst;
+ /** Marshal the {@code T1} inside of {@code Pair<T1, T2>} */
+ private final Marshaler<T2> mNestedTypeMarshalerSecond;
+
+ @SuppressWarnings("unchecked")
+ protected MarshalerPair(TypeReference<Pair<T1, T2>> typeReference,
+ int nativeType) {
+ super(MarshalQueryablePair.this, typeReference, nativeType);
+
+ mClass = typeReference.getRawType();
+
+ /*
+ * Lookup the actual type arguments, e.g. Pair<Integer, Float> --> [Integer, Float]
+ * and then get the marshalers for that managed type.
+ */
+ ParameterizedType paramType;
+ try {
+ paramType = (ParameterizedType) typeReference.getType();
+ } catch (ClassCastException e) {
+ throw new AssertionError("Raw use of Pair is not supported", e);
+ }
+
+ // Get type marshaler for T1
+ {
+ Type actualTypeArgument = paramType.getActualTypeArguments()[0];
+
+ TypeReference<?> actualTypeArgToken =
+ TypeReference.createSpecializedTypeReference(actualTypeArgument);
+
+ mNestedTypeMarshalerFirst = (Marshaler<T1>)MarshalRegistry.getMarshaler(
+ actualTypeArgToken, mNativeType);
+ }
+ // Get type marshaler for T2
+ {
+ Type actualTypeArgument = paramType.getActualTypeArguments()[1];
+
+ TypeReference<?> actualTypeArgToken =
+ TypeReference.createSpecializedTypeReference(actualTypeArgument);
+
+ mNestedTypeMarshalerSecond = (Marshaler<T2>)MarshalRegistry.getMarshaler(
+ actualTypeArgToken, mNativeType);
+ }
+ try {
+ mConstructor = (Constructor<Pair<T1, T2>>)mClass.getConstructor(
+ Object.class, Object.class);
+ } catch (NoSuchMethodException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public void marshal(Pair<T1, T2> value, ByteBuffer buffer) {
+ if (value.first == null) {
+ throw new UnsupportedOperationException("Pair#first must not be null");
+ } else if (value.second == null) {
+ throw new UnsupportedOperationException("Pair#second must not be null");
+ }
+
+ mNestedTypeMarshalerFirst.marshal(value.first, buffer);
+ mNestedTypeMarshalerSecond.marshal(value.second, buffer);
+ }
+
+ @Override
+ public Pair<T1, T2> unmarshal(ByteBuffer buffer) {
+ T1 first = mNestedTypeMarshalerFirst.unmarshal(buffer);
+ T2 second = mNestedTypeMarshalerSecond.unmarshal(buffer);
+
+ try {
+ return mConstructor.newInstance(first, second);
+ } catch (InstantiationException e) {
+ throw new AssertionError(e);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ } catch (IllegalArgumentException e) {
+ throw new AssertionError(e);
+ } catch (InvocationTargetException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public int getNativeSize() {
+ int firstSize = mNestedTypeMarshalerFirst.getNativeSize();
+ int secondSize = mNestedTypeMarshalerSecond.getNativeSize();
+
+ if (firstSize != NATIVE_SIZE_DYNAMIC && secondSize != NATIVE_SIZE_DYNAMIC) {
+ return firstSize + secondSize;
+ } else {
+ return NATIVE_SIZE_DYNAMIC;
+ }
+ }
+
+ @Override
+ public int calculateMarshalSize(Pair<T1, T2> value) {
+ int nativeSize = getNativeSize();
+
+ if (nativeSize != NATIVE_SIZE_DYNAMIC) {
+ return nativeSize;
+ } else {
+ int firstSize = mNestedTypeMarshalerFirst.calculateMarshalSize(value.first);
+ int secondSize = mNestedTypeMarshalerSecond.calculateMarshalSize(value.second);
+
+ return firstSize + secondSize;
+ }
+ }
+ }
+
+ @Override
+ public Marshaler<Pair<T1, T2>> createMarshaler(TypeReference<Pair<T1, T2>> managedType,
+ int nativeType) {
+ return new MarshalerPair(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<Pair<T1, T2>> managedType, int nativeType) {
+ return (Pair.class.equals(managedType.getRawType()));
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java
new file mode 100644
index 0000000..1fd6a1d
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.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 android.hardware.camera2.marshal.impl;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.utils.TypeReference;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+
+/**
+ * Marshal any {@code T extends Parcelable} to/from any native type
+ *
+ * <p>Use with extreme caution! File descriptors and binders will not be marshaled across.</p>
+ */
+public class MarshalQueryableParcelable<T extends Parcelable>
+ implements MarshalQueryable<T> {
+
+ private static final String TAG = "MarshalParcelable";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static final String FIELD_CREATOR = "CREATOR";
+
+ private class MarshalerParcelable extends Marshaler<T> {
+
+ private final Class<T> mClass;
+ private final Parcelable.Creator<T> mCreator;
+
+ @SuppressWarnings("unchecked")
+ protected MarshalerParcelable(TypeReference<T> typeReference,
+ int nativeType) {
+ super(MarshalQueryableParcelable.this, typeReference, nativeType);
+
+ mClass = (Class<T>)typeReference.getRawType();
+ Field creatorField;
+ try {
+ creatorField = mClass.getDeclaredField(FIELD_CREATOR);
+ } catch (NoSuchFieldException e) {
+ // Impossible. All Parcelable implementations must have a 'CREATOR' static field
+ throw new AssertionError(e);
+ }
+
+ try {
+ mCreator = (Parcelable.Creator<T>)creatorField.get(null);
+ } catch (IllegalAccessException e) {
+ // Impossible: All 'CREATOR' static fields must be public
+ throw new AssertionError(e);
+ } catch (IllegalArgumentException e) {
+ // Impossible: This is a static field, so null must be ok
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public void marshal(T value, ByteBuffer buffer) {
+ if (VERBOSE) {
+ Log.v(TAG, "marshal " + value);
+ }
+
+ Parcel parcel = Parcel.obtain();
+ byte[] parcelContents;
+
+ try {
+ value.writeToParcel(parcel, /*flags*/0);
+
+ if (parcel.hasFileDescriptors()) {
+ throw new UnsupportedOperationException(
+ "Parcelable " + value + " must not have file descriptors");
+ }
+
+ parcelContents = parcel.marshall();
+ }
+ finally {
+ parcel.recycle();
+ }
+
+ if (parcelContents.length == 0) {
+ throw new AssertionError("No data marshaled for " + value);
+ }
+
+ buffer.put(parcelContents);
+ }
+
+ @Override
+ public T unmarshal(ByteBuffer buffer) {
+ if (VERBOSE) {
+ Log.v(TAG, "unmarshal, buffer remaining " + buffer.remaining());
+ }
+
+ /*
+ * Quadratically slow when marshaling an array of parcelables.
+ *
+ * Read out the entire byte buffer as an array, then copy it into the parcel.
+ *
+ * Once we unparcel the entire object, advance the byte buffer by only how many
+ * bytes the parcel actually used up.
+ *
+ * Future: If we ever do need to use parcelable arrays, we can do this a little smarter
+ * by reading out a chunk like 4,8,16,24 each time, but not sure how to detect
+ * parcels being too short in this case.
+ *
+ * Future: Alternatively use Parcel#obtain(long) directly into the native
+ * pointer of a ByteBuffer, which would not copy if the ByteBuffer was direct.
+ */
+ buffer.mark();
+
+ Parcel parcel = Parcel.obtain();
+ try {
+ int maxLength = buffer.remaining();
+
+ byte[] remaining = new byte[maxLength];
+ buffer.get(remaining);
+
+ parcel.unmarshall(remaining, /*offset*/0, maxLength);
+ parcel.setDataPosition(/*pos*/0);
+
+ T value = mCreator.createFromParcel(parcel);
+ int actualLength = parcel.dataPosition();
+
+ if (actualLength == 0) {
+ throw new AssertionError("No data marshaled for " + value);
+ }
+
+ // set the position past the bytes the parcelable actually used
+ buffer.reset();
+ buffer.position(buffer.position() + actualLength);
+
+ if (VERBOSE) {
+ Log.v(TAG, "unmarshal, parcel length was " + actualLength);
+ Log.v(TAG, "unmarshal, value is " + value);
+ }
+
+ return mClass.cast(value);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ @Override
+ public int getNativeSize() {
+ return NATIVE_SIZE_DYNAMIC;
+ }
+
+ @Override
+ public int calculateMarshalSize(T value) {
+ Parcel parcel = Parcel.obtain();
+ try {
+ value.writeToParcel(parcel, /*flags*/0);
+ int length = parcel.marshall().length;
+
+ if (VERBOSE) {
+ Log.v(TAG, "calculateMarshalSize, length when parceling "
+ + value + " is " + length);
+ }
+
+ return length;
+ } finally {
+ parcel.recycle();
+ }
+ }
+ }
+
+ @Override
+ public Marshaler<T> createMarshaler(TypeReference<T> managedType, int nativeType) {
+ return new MarshalerParcelable(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<T> managedType, int nativeType) {
+ return Parcelable.class.isAssignableFrom(managedType.getRawType());
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePrimitive.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePrimitive.java
new file mode 100644
index 0000000..189b597
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePrimitive.java
@@ -0,0 +1,185 @@
+/*
+ * 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.marshal.impl;
+
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.utils.TypeReference;
+import android.util.Rational;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+import static com.android.internal.util.Preconditions.*;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Marshal/unmarshal built-in primitive types to and from a {@link ByteBuffer}.
+ *
+ * <p>The following list of type marshaling is supported:
+ * <ul>
+ * <li>byte <-> TYPE_BYTE
+ * <li>int <-> TYPE_INT32
+ * <li>long <-> TYPE_INT64
+ * <li>float <-> TYPE_FLOAT
+ * <li>double <-> TYPE_DOUBLE
+ * <li>Rational <-> TYPE_RATIONAL
+ * </ul>
+ * </p>
+ *
+ * <p>Due to the nature of generics, values are always boxed; this also means that both
+ * the boxed and unboxed types are supported (i.e. both {@code int} and {@code Integer}).</p>
+ *
+ * <p>Each managed type <!--(other than boolean)--> must correspond 1:1 to the native type
+ * (e.g. a byte will not map to a {@link CameraMetadataNative#TYPE_INT32 TYPE_INT32} or vice versa)
+ * for marshaling.</p>
+ */
+public final class MarshalQueryablePrimitive<T> implements MarshalQueryable<T> {
+
+ private class MarshalerPrimitive extends Marshaler<T> {
+ /** Always the wrapped class variant of the primitive class for {@code T} */
+ private final Class<T> mClass;
+
+ @SuppressWarnings("unchecked")
+ protected MarshalerPrimitive(TypeReference<T> typeReference, int nativeType) {
+ super(MarshalQueryablePrimitive.this, typeReference, nativeType);
+
+ // Turn primitives into wrappers, otherwise int.class.cast(Integer) will fail
+ mClass = wrapClassIfPrimitive((Class<T>)typeReference.getRawType());
+ }
+
+ @Override
+ public T unmarshal(ByteBuffer buffer) {
+ return mClass.cast(unmarshalObject(buffer));
+ }
+
+ @Override
+ public int calculateMarshalSize(T value) {
+ return getPrimitiveTypeSize(mNativeType);
+ }
+
+ @Override
+ public void marshal(T value, ByteBuffer buffer) {
+ if (value instanceof Integer) {
+ checkNativeTypeEquals(TYPE_INT32, mNativeType);
+ final int val = (Integer) value;
+ marshalPrimitive(val, buffer);
+ } else if (value instanceof Float) {
+ checkNativeTypeEquals(TYPE_FLOAT, mNativeType);
+ final float val = (Float) value;
+ marshalPrimitive(val, buffer);
+ } else if (value instanceof Long) {
+ checkNativeTypeEquals(TYPE_INT64, mNativeType);
+ final long val = (Long) value;
+ marshalPrimitive(val, buffer);
+ } else if (value instanceof Rational) {
+ checkNativeTypeEquals(TYPE_RATIONAL, mNativeType);
+ marshalPrimitive((Rational) value, buffer);
+ } else if (value instanceof Double) {
+ checkNativeTypeEquals(TYPE_DOUBLE, mNativeType);
+ final double val = (Double) value;
+ marshalPrimitive(val, buffer);
+ } else if (value instanceof Byte) {
+ checkNativeTypeEquals(TYPE_BYTE, mNativeType);
+ final byte val = (Byte) value;
+ marshalPrimitive(val, buffer);
+ } else {
+ throw new UnsupportedOperationException(
+ "Can't marshal managed type " + mTypeReference);
+ }
+ }
+
+ private void marshalPrimitive(int value, ByteBuffer buffer) {
+ buffer.putInt(value);
+ }
+
+ private void marshalPrimitive(float value, ByteBuffer buffer) {
+ buffer.putFloat(value);
+ }
+
+ private void marshalPrimitive(double value, ByteBuffer buffer) {
+ buffer.putDouble(value);
+ }
+
+ private void marshalPrimitive(long value, ByteBuffer buffer) {
+ buffer.putLong(value);
+ }
+
+ private void marshalPrimitive(Rational value, ByteBuffer buffer) {
+ buffer.putInt(value.getNumerator());
+ buffer.putInt(value.getDenominator());
+ }
+
+ private void marshalPrimitive(byte value, ByteBuffer buffer) {
+ buffer.put(value);
+ }
+
+ private Object unmarshalObject(ByteBuffer buffer) {
+ switch (mNativeType) {
+ case TYPE_INT32:
+ return buffer.getInt();
+ case TYPE_FLOAT:
+ return buffer.getFloat();
+ case TYPE_INT64:
+ return buffer.getLong();
+ case TYPE_RATIONAL:
+ int numerator = buffer.getInt();
+ int denominator = buffer.getInt();
+ return new Rational(numerator, denominator);
+ case TYPE_DOUBLE:
+ return buffer.getDouble();
+ case TYPE_BYTE:
+ return buffer.get(); // getByte
+ default:
+ throw new UnsupportedOperationException(
+ "Can't unmarshal native type " + mNativeType);
+ }
+ }
+
+ @Override
+ public int getNativeSize() {
+ return getPrimitiveTypeSize(mNativeType);
+ }
+ }
+
+ @Override
+ public Marshaler<T> createMarshaler(TypeReference<T> managedType, int nativeType) {
+ return new MarshalerPrimitive(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<T> managedType, int nativeType) {
+ if (managedType.getType() instanceof Class<?>) {
+ Class<?> klass = (Class<?>)managedType.getType();
+
+ if (klass == byte.class || klass == Byte.class) {
+ return nativeType == TYPE_BYTE;
+ } else if (klass == int.class || klass == Integer.class) {
+ return nativeType == TYPE_INT32;
+ } else if (klass == float.class || klass == Float.class) {
+ return nativeType == TYPE_FLOAT;
+ } else if (klass == long.class || klass == Long.class) {
+ return nativeType == TYPE_INT64;
+ } else if (klass == double.class || klass == Double.class) {
+ return nativeType == TYPE_DOUBLE;
+ } else if (klass == Rational.class) {
+ return nativeType == TYPE_RATIONAL;
+ }
+ }
+ return false;
+ }
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRange.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRange.java
new file mode 100644
index 0000000..8512804
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRange.java
@@ -0,0 +1,139 @@
+/*
+ * 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.marshal.impl;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.marshal.MarshalRegistry;
+import android.hardware.camera2.utils.TypeReference;
+import android.util.Range;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.nio.ByteBuffer;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+
+/**
+ * Marshal {@link Range} to/from any native type
+ */
+public class MarshalQueryableRange<T extends Comparable<? super T>>
+ implements MarshalQueryable<Range<T>> {
+ private static final int RANGE_COUNT = 2;
+
+ private class MarshalerRange extends Marshaler<Range<T>> {
+ private final Class<? super Range<T>> mClass;
+ private final Constructor<Range<T>> mConstructor;
+ /** Marshal the {@code T} inside of {@code Range<T>} */
+ private final Marshaler<T> mNestedTypeMarshaler;
+
+ @SuppressWarnings("unchecked")
+ protected MarshalerRange(TypeReference<Range<T>> typeReference,
+ int nativeType) {
+ super(MarshalQueryableRange.this, typeReference, nativeType);
+
+ mClass = typeReference.getRawType();
+
+ /*
+ * Lookup the actual type argument, e.g. Range<Integer> --> Integer
+ * and then get the marshaler for that managed type.
+ */
+ ParameterizedType paramType;
+ try {
+ paramType = (ParameterizedType) typeReference.getType();
+ } catch (ClassCastException e) {
+ throw new AssertionError("Raw use of Range is not supported", e);
+ }
+ Type actualTypeArgument = paramType.getActualTypeArguments()[0];
+
+ TypeReference<?> actualTypeArgToken =
+ TypeReference.createSpecializedTypeReference(actualTypeArgument);
+
+ mNestedTypeMarshaler = (Marshaler<T>)MarshalRegistry.getMarshaler(
+ actualTypeArgToken, mNativeType);
+ try {
+ mConstructor = (Constructor<Range<T>>)mClass.getConstructor(
+ Comparable.class, Comparable.class);
+ } catch (NoSuchMethodException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public void marshal(Range<T> value, ByteBuffer buffer) {
+ mNestedTypeMarshaler.marshal(value.getLower(), buffer);
+ mNestedTypeMarshaler.marshal(value.getUpper(), buffer);
+ }
+
+ @Override
+ public Range<T> unmarshal(ByteBuffer buffer) {
+ T lower = mNestedTypeMarshaler.unmarshal(buffer);
+ T upper = mNestedTypeMarshaler.unmarshal(buffer);
+
+ try {
+ return mConstructor.newInstance(lower, upper);
+ } catch (InstantiationException e) {
+ throw new AssertionError(e);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ } catch (IllegalArgumentException e) {
+ throw new AssertionError(e);
+ } catch (InvocationTargetException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public int getNativeSize() {
+ int nestedSize = mNestedTypeMarshaler.getNativeSize();
+
+ if (nestedSize != NATIVE_SIZE_DYNAMIC) {
+ return nestedSize * RANGE_COUNT;
+ } else {
+ return NATIVE_SIZE_DYNAMIC;
+ }
+ }
+
+ @Override
+ public int calculateMarshalSize(Range<T> value) {
+ int nativeSize = getNativeSize();
+
+ if (nativeSize != NATIVE_SIZE_DYNAMIC) {
+ return nativeSize;
+ } else {
+ int lowerSize = mNestedTypeMarshaler.calculateMarshalSize(value.getLower());
+ int upperSize = mNestedTypeMarshaler.calculateMarshalSize(value.getUpper());
+
+ return lowerSize + upperSize;
+ }
+ }
+ }
+
+ @Override
+ public Marshaler<Range<T>> createMarshaler(TypeReference<Range<T>> managedType,
+ int nativeType) {
+ return new MarshalerRange(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<Range<T>> managedType, int nativeType) {
+ return (Range.class.equals(managedType.getRawType()));
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRect.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRect.java
new file mode 100644
index 0000000..de20a1f
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRect.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.hardware.camera2.marshal.impl;
+
+import android.graphics.Rect;
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.utils.TypeReference;
+
+import java.nio.ByteBuffer;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+
+/**
+ * Marshal {@link Rect} to/from {@link #TYPE_INT32}
+ */
+public class MarshalQueryableRect implements MarshalQueryable<Rect> {
+ private static final int SIZE = SIZEOF_INT32 * 4;
+
+ private class MarshalerRect extends Marshaler<Rect> {
+ protected MarshalerRect(TypeReference<Rect> typeReference,
+ int nativeType) {
+ super(MarshalQueryableRect.this, typeReference, nativeType);
+ }
+
+ @Override
+ public void marshal(Rect value, ByteBuffer buffer) {
+ buffer.putInt(value.left);
+ buffer.putInt(value.top);
+ buffer.putInt(value.width());
+ buffer.putInt(value.height());
+ }
+
+ @Override
+ public Rect unmarshal(ByteBuffer buffer) {
+ int left = buffer.getInt();
+ int top = buffer.getInt();
+ int width = buffer.getInt();
+ int height = buffer.getInt();
+
+ int right = left + width;
+ int bottom = top + height;
+
+ return new Rect(left, top, right, bottom);
+ }
+
+ @Override
+ public int getNativeSize() {
+ return SIZE;
+ }
+ }
+
+ @Override
+ public Marshaler<Rect> createMarshaler(TypeReference<Rect> managedType, int nativeType) {
+ return new MarshalerRect(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<Rect> managedType, int nativeType) {
+ return nativeType == TYPE_INT32 && (Rect.class.equals(managedType.getType()));
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableReprocessFormatsMap.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableReprocessFormatsMap.java
new file mode 100644
index 0000000..98a7ad7
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableReprocessFormatsMap.java
@@ -0,0 +1,131 @@
+/*
+ * 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.marshal.impl;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.params.ReprocessFormatsMap;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.utils.TypeReference;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+
+/**
+ * Marshaler for {@code android.scaler.availableInputOutputFormatsMap} custom class
+ * {@link ReprocessFormatsMap}
+ */
+public class MarshalQueryableReprocessFormatsMap
+ implements MarshalQueryable<ReprocessFormatsMap> {
+
+ private class MarshalerReprocessFormatsMap extends Marshaler<ReprocessFormatsMap> {
+ protected MarshalerReprocessFormatsMap(
+ TypeReference<ReprocessFormatsMap> typeReference, int nativeType) {
+ super(MarshalQueryableReprocessFormatsMap.this, typeReference, nativeType);
+ }
+
+ @Override
+ public void marshal(ReprocessFormatsMap value, ByteBuffer buffer) {
+ /*
+ * // writing (static example, DNG+ZSL)
+ * int32_t[] contents = {
+ * RAW_OPAQUE, 3, RAW16, YUV_420_888, BLOB,
+ * RAW16, 2, YUV_420_888, BLOB,
+ * ...,
+ * INPUT_FORMAT, OUTPUT_FORMAT_COUNT, [OUTPUT_0, OUTPUT_1, ..., OUTPUT_FORMAT_COUNT-1]
+ * };
+ */
+ int[] inputs = StreamConfigurationMap.imageFormatToInternal(value.getInputs());
+ for (int input : inputs) {
+ // INPUT_FORMAT
+ buffer.putInt(input);
+
+ int[] outputs =
+ StreamConfigurationMap.imageFormatToInternal(value.getOutputs(input));
+ // OUTPUT_FORMAT_COUNT
+ buffer.putInt(outputs.length);
+
+ // [OUTPUT_0, OUTPUT_1, ..., OUTPUT_FORMAT_COUNT-1]
+ for (int output : outputs) {
+ buffer.putInt(output);
+ }
+ }
+ }
+
+ @Override
+ public ReprocessFormatsMap unmarshal(ByteBuffer buffer) {
+ int len = buffer.remaining() / SIZEOF_INT32;
+ if (buffer.remaining() % SIZEOF_INT32 != 0) {
+ throw new AssertionError("ReprocessFormatsMap was not TYPE_INT32");
+ }
+
+ int[] entries = new int[len];
+
+ IntBuffer intBuffer = buffer.asIntBuffer();
+ intBuffer.get(entries);
+
+ // TODO: consider moving rest of parsing code from ReprocessFormatsMap to here
+
+ return new ReprocessFormatsMap(entries);
+ }
+
+ @Override
+ public int getNativeSize() {
+ return NATIVE_SIZE_DYNAMIC;
+ }
+
+ @Override
+ public int calculateMarshalSize(ReprocessFormatsMap value) {
+ /*
+ * // writing (static example, DNG+ZSL)
+ * int32_t[] contents = {
+ * RAW_OPAQUE, 3, RAW16, YUV_420_888, BLOB,
+ * RAW16, 2, YUV_420_888, BLOB,
+ * ...,
+ * INPUT_FORMAT, OUTPUT_FORMAT_COUNT, [OUTPUT_0, OUTPUT_1, ..., OUTPUT_FORMAT_COUNT-1]
+ * };
+ */
+ int length = 0;
+
+ int[] inputs = value.getInputs();
+ for (int input : inputs) {
+
+ length += 1; // INPUT_FORMAT
+ length += 1; // OUTPUT_FORMAT_COUNT
+
+ int[] outputs = value.getOutputs(input);
+ length += outputs.length; // [OUTPUT_0, OUTPUT_1, ..., OUTPUT_FORMAT_COUNT-1]
+ }
+
+ return length * SIZEOF_INT32;
+ }
+ }
+
+ @Override
+ public Marshaler<ReprocessFormatsMap> createMarshaler(
+ TypeReference<ReprocessFormatsMap> managedType, int nativeType) {
+ return new MarshalerReprocessFormatsMap(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<ReprocessFormatsMap> managedType,
+ int nativeType) {
+ return nativeType == TYPE_INT32 && managedType.getType().equals(ReprocessFormatsMap.class);
+ }
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRggbChannelVector.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRggbChannelVector.java
new file mode 100644
index 0000000..4253a0a
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRggbChannelVector.java
@@ -0,0 +1,75 @@
+/*
+ * 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.marshal.impl;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.params.RggbChannelVector;
+import android.hardware.camera2.utils.TypeReference;
+
+import java.nio.ByteBuffer;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+
+/**
+ * Marshal {@link RggbChannelVector} to/from {@link #TYPE_FLOAT} {@code x 4}
+ */
+public class MarshalQueryableRggbChannelVector implements MarshalQueryable<RggbChannelVector> {
+ private static final int SIZE = SIZEOF_FLOAT * RggbChannelVector.COUNT;
+
+ private class MarshalerRggbChannelVector extends Marshaler<RggbChannelVector> {
+ protected MarshalerRggbChannelVector(TypeReference<RggbChannelVector> typeReference,
+ int nativeType) {
+ super(MarshalQueryableRggbChannelVector.this, typeReference, nativeType);
+ }
+
+ @Override
+ public void marshal(RggbChannelVector value, ByteBuffer buffer) {
+ for (int i = 0; i < RggbChannelVector.COUNT; ++i) {
+ buffer.putFloat(value.getComponent(i));
+ }
+ }
+
+ @Override
+ public RggbChannelVector unmarshal(ByteBuffer buffer) {
+ float red = buffer.getFloat();
+ float gEven = buffer.getFloat();
+ float gOdd = buffer.getFloat();
+ float blue = buffer.getFloat();
+
+ return new RggbChannelVector(red, gEven, gOdd, blue);
+ }
+
+ @Override
+ public int getNativeSize() {
+ return SIZE;
+ }
+ }
+
+ @Override
+ public Marshaler<RggbChannelVector> createMarshaler(
+ TypeReference<RggbChannelVector> managedType, int nativeType) {
+ return new MarshalerRggbChannelVector(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(
+ TypeReference<RggbChannelVector> managedType, int nativeType) {
+ return nativeType == TYPE_FLOAT && (RggbChannelVector.class.equals(managedType.getType()));
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableSize.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableSize.java
new file mode 100644
index 0000000..721644e
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableSize.java
@@ -0,0 +1,68 @@
+/*
+ * 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.marshal.impl;
+
+import android.util.Size;
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.utils.TypeReference;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Marshal {@link Size} to/from {@code TYPE_INT32}
+ */
+public class MarshalQueryableSize implements MarshalQueryable<Size> {
+ private static final int SIZE = SIZEOF_INT32 * 2;
+
+ private class MarshalerSize extends Marshaler<Size> {
+ protected MarshalerSize(TypeReference<Size> typeReference, int nativeType) {
+ super(MarshalQueryableSize.this, typeReference, nativeType);
+ }
+
+ @Override
+ public void marshal(Size value, ByteBuffer buffer) {
+ buffer.putInt(value.getWidth());
+ buffer.putInt(value.getHeight());
+ }
+
+ @Override
+ public Size unmarshal(ByteBuffer buffer) {
+ int width = buffer.getInt();
+ int height = buffer.getInt();
+
+ return new Size(width, height);
+ }
+
+ @Override
+ public int getNativeSize() {
+ return SIZE;
+ }
+ }
+
+ @Override
+ public Marshaler<Size> createMarshaler(TypeReference<Size> managedType, int nativeType) {
+ return new MarshalerSize(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<Size> managedType, int nativeType) {
+ return nativeType == TYPE_INT32 && (Size.class.equals(managedType.getType()));
+ }
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableSizeF.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableSizeF.java
new file mode 100644
index 0000000..b60a46d
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableSizeF.java
@@ -0,0 +1,72 @@
+/*
+ * 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.marshal.impl;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.utils.TypeReference;
+import android.util.SizeF;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Marshal {@link SizeF} to/from {@code TYPE_FLOAT}
+ */
+public class MarshalQueryableSizeF implements MarshalQueryable<SizeF> {
+
+ private static final int SIZE = SIZEOF_FLOAT * 2;
+
+ private class MarshalerSizeF extends Marshaler<SizeF> {
+
+ protected MarshalerSizeF(TypeReference<SizeF> typeReference, int nativeType) {
+ super(MarshalQueryableSizeF.this, typeReference, nativeType);
+ }
+
+ @Override
+ public void marshal(SizeF value, ByteBuffer buffer) {
+ buffer.putFloat(value.getWidth());
+ buffer.putFloat(value.getHeight());
+ }
+
+ @Override
+ public SizeF unmarshal(ByteBuffer buffer) {
+ float width = buffer.getFloat();
+ float height = buffer.getFloat();
+
+ return new SizeF(width, height);
+ }
+
+ @Override
+ public int getNativeSize() {
+ return SIZE;
+ }
+ }
+
+ @Override
+ public Marshaler<SizeF> createMarshaler(
+ TypeReference<SizeF> managedType, int nativeType) {
+ return new MarshalerSizeF(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<SizeF> managedType, int nativeType) {
+ return nativeType == TYPE_FLOAT && (SizeF.class.equals(managedType.getType()));
+ }
+}
+
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableStreamConfiguration.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableStreamConfiguration.java
new file mode 100644
index 0000000..62ace31
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableStreamConfiguration.java
@@ -0,0 +1,80 @@
+/*
+ * 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.marshal.impl;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.params.StreamConfiguration;
+import android.hardware.camera2.utils.TypeReference;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Marshaler for {@code android.scaler.availableStreamConfigurations} custom class
+ * {@link StreamConfiguration}
+ *
+ * <p>Data is stored as {@code (format, width, height, input?)} tuples (int32).</p>
+ */
+public class MarshalQueryableStreamConfiguration
+ implements MarshalQueryable<StreamConfiguration> {
+ private static final int SIZE = SIZEOF_INT32 * 4;
+
+ private class MarshalerStreamConfiguration extends Marshaler<StreamConfiguration> {
+ protected MarshalerStreamConfiguration(TypeReference<StreamConfiguration> typeReference,
+ int nativeType) {
+ super(MarshalQueryableStreamConfiguration.this, typeReference, nativeType);
+ }
+
+ @Override
+ public void marshal(StreamConfiguration value, ByteBuffer buffer) {
+ buffer.putInt(value.getFormat());
+ buffer.putInt(value.getWidth());
+ buffer.putInt(value.getHeight());
+ buffer.putInt(value.isInput() ? 1 : 0);
+ }
+
+ @Override
+ public StreamConfiguration unmarshal(ByteBuffer buffer) {
+ int format = buffer.getInt();
+ int width = buffer.getInt();
+ int height = buffer.getInt();
+ boolean input = buffer.getInt() != 0;
+
+ return new StreamConfiguration(format, width, height, input);
+ }
+
+ @Override
+ public int getNativeSize() {
+ return SIZE;
+ }
+
+ }
+
+ @Override
+ public Marshaler<StreamConfiguration> createMarshaler(
+ TypeReference<StreamConfiguration> managedType, int nativeType) {
+ return new MarshalerStreamConfiguration(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<StreamConfiguration> managedType,
+ int nativeType) {
+ return nativeType == TYPE_INT32 && managedType.getType().equals(StreamConfiguration.class);
+ }
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableStreamConfigurationDuration.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableStreamConfigurationDuration.java
new file mode 100644
index 0000000..fd3dfac
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableStreamConfigurationDuration.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.marshal.impl;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.params.StreamConfigurationDuration;
+import android.hardware.camera2.utils.TypeReference;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+import static android.hardware.camera2.marshal.MarshalHelpers.*;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Marshaler for custom class {@link StreamConfigurationDuration} for min-frame and stall durations.
+ *
+ * <p>
+ * Data is stored as {@code (format, width, height, durationNs)} tuples (int64).
+ * </p>
+ */
+public class MarshalQueryableStreamConfigurationDuration
+ implements MarshalQueryable<StreamConfigurationDuration> {
+
+ private static final int SIZE = SIZEOF_INT64 * 4;
+ /**
+ * Values and-ed with this will do an unsigned int to signed long conversion;
+ * in other words the sign bit from the int will not be extended.
+ * */
+ private static final long MASK_UNSIGNED_INT = 0x00000000ffffffffL;
+
+ private class MarshalerStreamConfigurationDuration
+ extends Marshaler<StreamConfigurationDuration> {
+
+ protected MarshalerStreamConfigurationDuration(
+ TypeReference<StreamConfigurationDuration> typeReference, int nativeType) {
+ super(MarshalQueryableStreamConfigurationDuration.this, typeReference, nativeType);
+ }
+
+ @Override
+ public void marshal(StreamConfigurationDuration value, ByteBuffer buffer) {
+ buffer.putLong(value.getFormat() & MASK_UNSIGNED_INT); // unsigned int -> long
+ buffer.putLong(value.getWidth());
+ buffer.putLong(value.getHeight());
+ buffer.putLong(value.getDuration());
+ }
+
+ @Override
+ public StreamConfigurationDuration unmarshal(ByteBuffer buffer) {
+ int format = (int)buffer.getLong();
+ int width = (int)buffer.getLong();
+ int height = (int)buffer.getLong();
+ long durationNs = buffer.getLong();
+
+ return new StreamConfigurationDuration(format, width, height, durationNs);
+ }
+
+ @Override
+ public int getNativeSize() {
+ return SIZE;
+ }
+ }
+
+ @Override
+ public Marshaler<StreamConfigurationDuration> createMarshaler(
+ TypeReference<StreamConfigurationDuration> managedType, int nativeType) {
+ return new MarshalerStreamConfigurationDuration(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<StreamConfigurationDuration> managedType,
+ int nativeType) {
+ return nativeType == TYPE_INT64 &&
+ (StreamConfigurationDuration.class.equals(managedType.getType()));
+ }
+
+} \ No newline at end of file
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableString.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableString.java
new file mode 100644
index 0000000..bf518bb
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableString.java
@@ -0,0 +1,110 @@
+/*
+ * 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.marshal.impl;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.utils.TypeReference;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
+
+/**
+ * Marshal {@link String} to/from {@link #TYPE_BYTE}.
+ */
+public class MarshalQueryableString implements MarshalQueryable<String> {
+
+ private static final String TAG = MarshalQueryableString.class.getSimpleName();
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
+ private static final byte NUL = (byte)'\0'; // used as string terminator
+
+ private class MarshalerString extends Marshaler<String> {
+
+ protected MarshalerString(TypeReference<String> typeReference, int nativeType) {
+ super(MarshalQueryableString.this, typeReference, nativeType);
+ }
+
+ @Override
+ public void marshal(String value, ByteBuffer buffer) {
+ byte[] arr = value.getBytes(UTF8_CHARSET);
+
+ buffer.put(arr);
+ buffer.put(NUL); // metadata strings are NUL-terminated
+ }
+
+ @Override
+ public int calculateMarshalSize(String value) {
+ byte[] arr = value.getBytes(UTF8_CHARSET);
+
+ return arr.length + 1; // metadata strings are NUL-terminated
+ }
+
+ @Override
+ public String unmarshal(ByteBuffer buffer) {
+ buffer.mark(); // save the current position
+
+ boolean foundNull = false;
+ int stringLength = 0;
+ while (buffer.hasRemaining()) {
+ if (buffer.get() == NUL) {
+ foundNull = true;
+ break;
+ }
+
+ stringLength++;
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG,
+ "unmarshal - scanned " + stringLength + " characters; found null? "
+ + foundNull);
+ }
+
+ if (!foundNull) {
+ throw new UnsupportedOperationException("Strings must be null-terminated");
+ }
+
+ buffer.reset(); // go back to the previously marked position
+
+ byte[] strBytes = new byte[stringLength + 1];
+ buffer.get(strBytes, /*dstOffset*/0, stringLength + 1); // including null character
+
+ // not including null character
+ return new String(strBytes, /*offset*/0, stringLength, UTF8_CHARSET);
+ }
+
+ @Override
+ public int getNativeSize() {
+ return NATIVE_SIZE_DYNAMIC;
+ }
+ }
+
+ @Override
+ public Marshaler<String> createMarshaler(
+ TypeReference<String> managedType, int nativeType) {
+ return new MarshalerString(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<String> managedType, int nativeType) {
+ return nativeType == TYPE_BYTE && String.class.equals(managedType.getType());
+ }
+}
diff --git a/core/java/android/hardware/camera2/marshal/impl/package.html b/core/java/android/hardware/camera2/marshal/impl/package.html
new file mode 100644
index 0000000..783d0a1
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/package.html
@@ -0,0 +1,3 @@
+<body>
+{@hide}
+</body>
diff --git a/core/java/android/hardware/camera2/marshal/package.html b/core/java/android/hardware/camera2/marshal/package.html
new file mode 100644
index 0000000..783d0a1
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/package.html
@@ -0,0 +1,3 @@
+<body>
+{@hide}
+</body>
diff --git a/core/java/android/hardware/camera2/ColorSpaceTransform.java b/core/java/android/hardware/camera2/params/ColorSpaceTransform.java
index 9912e4b..b4289db 100644
--- a/core/java/android/hardware/camera2/ColorSpaceTransform.java
+++ b/core/java/android/hardware/camera2/params/ColorSpaceTransform.java
@@ -14,10 +14,13 @@
* limitations under the License.
*/
-package android.hardware.camera2;
+package android.hardware.camera2.params;
import static com.android.internal.util.Preconditions.*;
-import android.hardware.camera2.impl.HashCodeHelpers;
+
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.utils.HashCodeHelpers;
+import android.util.Rational;
import java.util.Arrays;
@@ -136,8 +139,8 @@ public final class ColorSpaceTransform {
throw new IllegalArgumentException("row out of range");
}
- int numerator = mElements[row * ROWS * RATIONAL_SIZE + column + OFFSET_NUMERATOR];
- int denominator = mElements[row * ROWS * RATIONAL_SIZE + column + OFFSET_DENOMINATOR];
+ int numerator = mElements[(row * COLUMNS + column) * RATIONAL_SIZE + OFFSET_NUMERATOR];
+ int denominator = mElements[(row * COLUMNS + column) * RATIONAL_SIZE + OFFSET_DENOMINATOR];
return new Rational(numerator, denominator);
}
@@ -159,7 +162,7 @@ public final class ColorSpaceTransform {
public void copyElements(Rational[] destination, int offset) {
checkArgumentNonnegative(offset, "offset must not be negative");
checkNotNull(destination, "destination must not be null");
- if (destination.length + offset < COUNT) {
+ if (destination.length - offset < COUNT) {
throw new ArrayIndexOutOfBoundsException("destination too small to fit elements");
}
@@ -194,7 +197,7 @@ public final class ColorSpaceTransform {
public void copyElements(int[] destination, int offset) {
checkArgumentNonnegative(offset, "offset must not be negative");
checkNotNull(destination, "destination must not be null");
- if (destination.length + offset < COUNT_INT) {
+ if (destination.length - offset < COUNT_INT) {
throw new ArrayIndexOutOfBoundsException("destination too small to fit elements");
}
diff --git a/core/java/android/hardware/camera2/Face.java b/core/java/android/hardware/camera2/params/Face.java
index ded8839d..2cd83a3 100644
--- a/core/java/android/hardware/camera2/Face.java
+++ b/core/java/android/hardware/camera2/params/Face.java
@@ -15,10 +15,13 @@
*/
-package android.hardware.camera2;
+package android.hardware.camera2.params;
import android.graphics.Point;
import android.graphics.Rect;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureResult;
/**
* Describes a face detected in an image.
diff --git a/core/java/android/hardware/camera2/LensShadingMap.java b/core/java/android/hardware/camera2/params/LensShadingMap.java
index 2c224f6..9bbc33a 100644
--- a/core/java/android/hardware/camera2/LensShadingMap.java
+++ b/core/java/android/hardware/camera2/params/LensShadingMap.java
@@ -14,19 +14,21 @@
* limitations under the License.
*/
-package android.hardware.camera2;
+package android.hardware.camera2.params;
import static com.android.internal.util.Preconditions.*;
-import static android.hardware.camera2.RggbChannelVector.*;
+import static android.hardware.camera2.params.RggbChannelVector.*;
-import android.hardware.camera2.impl.HashCodeHelpers;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.utils.HashCodeHelpers;
import java.util.Arrays;
/**
* Immutable class for describing a {@code 4 x N x M} lens shading map of floats.
*
- * @see CameraCharacteristics#LENS_SHADING_MAP
+ * @see CaptureResult#STATISTICS_LENS_SHADING_CORRECTION_MAP
*/
public final class LensShadingMap {
@@ -60,12 +62,12 @@ public final class LensShadingMap {
public LensShadingMap(final float[] elements, final int rows, final int columns) {
mRows = checkArgumentPositive(rows, "rows must be positive");
- mColumns = checkArgumentPositive(rows, "columns must be positive");
+ mColumns = checkArgumentPositive(columns, "columns must be positive");
mElements = checkNotNull(elements, "elements must not be null");
if (elements.length != getGainFactorCount()) {
throw new IllegalArgumentException("elements must be " + getGainFactorCount() +
- " length");
+ " length, received " + elements.length);
}
// Every element must be finite and >= 1.0f
@@ -240,4 +242,4 @@ public final class LensShadingMap {
private final int mRows;
private final int mColumns;
private final float[] mElements;
-};
+}
diff --git a/core/java/android/hardware/camera2/MeteringRectangle.java b/core/java/android/hardware/camera2/params/MeteringRectangle.java
index ff7a745..93fd053 100644
--- a/core/java/android/hardware/camera2/MeteringRectangle.java
+++ b/core/java/android/hardware/camera2/params/MeteringRectangle.java
@@ -13,32 +13,63 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.hardware.camera2;
+
+package android.hardware.camera2.params;
import android.util.Size;
import static com.android.internal.util.Preconditions.*;
import android.graphics.Point;
import android.graphics.Rect;
-import android.hardware.camera2.impl.HashCodeHelpers;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.utils.HashCodeHelpers;
/**
- * An immutable class to represent a rectangle {@code (x,y, width, height)} with an
- * additional weight component.
- *
- * </p>The rectangle is defined to be inclusive of the specified coordinates.</p>
- *
- * <p>When used with a {@link CaptureRequest}, the coordinate system is based on the active pixel
+ * An immutable class to represent a rectangle {@code (x, y, width, height)} with an additional
+ * weight component.
+ * <p>
+ * The rectangle is defined to be inclusive of the specified coordinates.
+ * </p>
+ * <p>
+ * When used with a {@link CaptureRequest}, the coordinate system is based on the active pixel
* array, with {@code (0,0)} being the top-left pixel in the
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE active pixel array}, and
* {@code (android.sensor.info.activeArraySize.width - 1,
- * android.sensor.info.activeArraySize.height - 1)}
- * being the bottom-right pixel in the active pixel array.
+ * android.sensor.info.activeArraySize.height - 1)} being the bottom-right pixel in the active pixel
+ * array.
+ * </p>
+ * <p>
+ * The weight must range from {@value #METERING_WEIGHT_MIN} to {@value #METERING_WEIGHT_MAX}
+ * inclusively, and represents a weight for every pixel in the area. This means that a large
+ * metering area with the same weight as a smaller area will have more effect in the metering
+ * result. Metering areas can partially overlap and the camera device will add the weights in the
+ * overlap rectangle.
+ * </p>
+ * <p>
+ * If all rectangles have 0 weight, then no specific metering area needs to be used by the camera
+ * device. If the metering rectangle is outside the used android.scaler.cropRegion returned in
+ * capture result metadata, the camera device will ignore the sections outside the rectangle and
+ * output the used sections in the result metadata.
* </p>
- *
- * <p>The metering weight is nonnegative.</p>
*/
public final class MeteringRectangle {
+ /**
+ * The minimum value of valid metering weight.
+ */
+ public static final int METERING_WEIGHT_MIN = 0;
+
+ /**
+ * The maximum value of valid metering weight.
+ */
+ public static final int METERING_WEIGHT_MAX = 1000;
+
+ /**
+ * Weights set to this value will cause the camera device to ignore this rectangle.
+ * If all metering rectangles are weighed with 0, the camera device will choose its own metering
+ * rectangles.
+ */
+ public static final int METERING_WEIGHT_DONT_CARE = 0;
private final int mX;
private final int mY;
@@ -53,16 +84,17 @@ public final class MeteringRectangle {
* @param y coordinate >= 0
* @param width width >= 0
* @param height height >= 0
- * @param meteringWeight weight >= 0
- *
- * @throws IllegalArgumentException if any of the parameters were non-negative
+ * @param meteringWeight weight between {@value #METERING_WEIGHT_MIN} and
+ * {@value #METERING_WEIGHT_MAX} inclusively
+ * @throws IllegalArgumentException if any of the parameters were negative
*/
public MeteringRectangle(int x, int y, int width, int height, int meteringWeight) {
mX = checkArgumentNonnegative(x, "x must be nonnegative");
mY = checkArgumentNonnegative(y, "y must be nonnegative");
mWidth = checkArgumentNonnegative(width, "width must be nonnegative");
mHeight = checkArgumentNonnegative(height, "height must be nonnegative");
- mWeight = checkArgumentNonnegative(meteringWeight, "meteringWeight must be nonnegative");
+ mWeight = checkArgumentInRange(
+ meteringWeight, METERING_WEIGHT_MIN, METERING_WEIGHT_MAX, "meteringWeight");
}
/**
@@ -72,7 +104,7 @@ public final class MeteringRectangle {
* @param dimensions a non-{@code null} {@link android.util.Size Size} with width, height >= 0
* @param meteringWeight weight >= 0
*
- * @throws IllegalArgumentException if any of the parameters were non-negative
+ * @throws IllegalArgumentException if any of the parameters were negative
* @throws NullPointerException if any of the arguments were null
*/
public MeteringRectangle(Point xy, Size dimensions, int meteringWeight) {
@@ -92,7 +124,7 @@ public final class MeteringRectangle {
* @param rect a non-{@code null} rectangle with all x,y,w,h dimensions >= 0
* @param meteringWeight weight >= 0
*
- * @throws IllegalArgumentException if any of the parameters were non-negative
+ * @throws IllegalArgumentException if any of the parameters were negative
* @throws NullPointerException if any of the arguments were null
*/
public MeteringRectangle(Rect rect, int meteringWeight) {
@@ -186,10 +218,7 @@ public final class MeteringRectangle {
*/
@Override
public boolean equals(final Object other) {
- if (other instanceof MeteringRectangle) {
- return equals(other);
- }
- return false;
+ return other instanceof MeteringRectangle && equals((MeteringRectangle)other);
}
/**
@@ -211,7 +240,7 @@ public final class MeteringRectangle {
&& mY == other.mY
&& mWidth == other.mWidth
&& mHeight == other.mHeight
- && mWidth == other.mWidth);
+ && mWeight == other.mWeight);
}
/**
diff --git a/core/java/android/hardware/camera2/ReprocessFormatsMap.java b/core/java/android/hardware/camera2/params/ReprocessFormatsMap.java
index c6c59d4..d3f5bc3 100644
--- a/core/java/android/hardware/camera2/ReprocessFormatsMap.java
+++ b/core/java/android/hardware/camera2/params/ReprocessFormatsMap.java
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package android.hardware.camera2;
+package android.hardware.camera2.params;
import static com.android.internal.util.Preconditions.*;
-import android.hardware.camera2.impl.HashCodeHelpers;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.utils.HashCodeHelpers;
import java.util.Arrays;
@@ -61,9 +62,12 @@ public final class ReprocessFormatsMap {
* @throws IllegalArgumentException
* if the data was poorly formatted
* (missing output format length or too few output formats)
+ * or if any of the input/formats were not valid
* @throws NullPointerException
* if entry was null
*
+ * @see StreamConfigurationMap#checkArgumentFormatInternal
+ *
* @hide
*/
public ReprocessFormatsMap(final int[] entry) {
@@ -72,26 +76,31 @@ public final class ReprocessFormatsMap {
int numInputs = 0;
int left = entry.length;
for (int i = 0; i < entry.length; ) {
- final int format = entry[i];
+ int inputFormat = StreamConfigurationMap.checkArgumentFormatInternal(entry[i]);
left--;
i++;
if (left < 1) {
throw new IllegalArgumentException(
- String.format("Input %x had no output format length listed", format));
+ String.format("Input %x had no output format length listed", inputFormat));
}
final int length = entry[i];
left--;
i++;
+ for (int j = 0; j < length; ++j) {
+ int outputFormat = entry[i + j];
+ StreamConfigurationMap.checkArgumentFormatInternal(outputFormat);
+ }
+
if (length > 0) {
if (left < length) {
throw new IllegalArgumentException(
String.format(
"Input %x had too few output formats listed (actual: %d, " +
- "expected: %d)", format, left, length));
+ "expected: %d)", inputFormat, left, length));
}
i += length;
@@ -131,7 +140,6 @@ public final class ReprocessFormatsMap {
throw new AssertionError(
String.format("Input %x had no output format length listed", format));
}
- // TODO: check format is a valid input format
final int length = mEntry[i];
left--;
@@ -149,12 +157,10 @@ public final class ReprocessFormatsMap {
left -= length;
}
- // TODO: check output format is a valid output format
-
inputs[j] = format;
}
- return inputs;
+ return StreamConfigurationMap.imageFormatToPublic(inputs);
}
/**
@@ -204,7 +210,7 @@ public final class ReprocessFormatsMap {
outputs[k] = mEntry[i + k];
}
- return outputs;
+ return StreamConfigurationMap.imageFormatToPublic(outputs);
}
i += length;
diff --git a/core/java/android/hardware/camera2/RggbChannelVector.java b/core/java/android/hardware/camera2/params/RggbChannelVector.java
index 80167c6..30591f6 100644
--- a/core/java/android/hardware/camera2/RggbChannelVector.java
+++ b/core/java/android/hardware/camera2/params/RggbChannelVector.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.hardware.camera2;
+package android.hardware.camera2.params;
import static com.android.internal.util.Preconditions.*;
diff --git a/core/java/android/hardware/camera2/StreamConfiguration.java b/core/java/android/hardware/camera2/params/StreamConfiguration.java
index c53dd7c..1c6b6e9 100644
--- a/core/java/android/hardware/camera2/StreamConfiguration.java
+++ b/core/java/android/hardware/camera2/params/StreamConfiguration.java
@@ -14,13 +14,16 @@
* limitations under the License.
*/
-package android.hardware.camera2;
+package android.hardware.camera2.params;
import static com.android.internal.util.Preconditions.*;
-import static android.hardware.camera2.StreamConfigurationMap.checkArgumentFormatInternal;
+import static android.hardware.camera2.params.StreamConfigurationMap.checkArgumentFormatInternal;
import android.graphics.ImageFormat;
-import android.hardware.camera2.impl.HashCodeHelpers;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.utils.HashCodeHelpers;
+import android.graphics.PixelFormat;
import android.util.Size;
/**
@@ -57,16 +60,17 @@ public final class StreamConfiguration {
final int format, final int width, final int height, final boolean input) {
mFormat = checkArgumentFormatInternal(format);
mWidth = checkArgumentPositive(width, "width must be positive");
- mHeight = checkArgumentPositive(width, "height must be positive");
+ mHeight = checkArgumentPositive(height, "height must be positive");
mInput = input;
}
/**
- * Get the image {@code format} in this stream configuration.
+ * Get the internal image {@code format} in this stream configuration.
*
* @return an integer format
*
* @see ImageFormat
+ * @see PixelFormat
*/
public final int getFormat() {
return mFormat;
diff --git a/core/java/android/hardware/camera2/StreamConfigurationDuration.java b/core/java/android/hardware/camera2/params/StreamConfigurationDuration.java
index 189ae62..217059d 100644
--- a/core/java/android/hardware/camera2/StreamConfigurationDuration.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationDuration.java
@@ -14,13 +14,15 @@
* limitations under the License.
*/
-package android.hardware.camera2;
+package android.hardware.camera2.params;
import static com.android.internal.util.Preconditions.*;
-import static android.hardware.camera2.StreamConfigurationMap.checkArgumentFormatInternal;
+import static android.hardware.camera2.params.StreamConfigurationMap.checkArgumentFormatInternal;
import android.graphics.ImageFormat;
-import android.hardware.camera2.impl.HashCodeHelpers;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.utils.HashCodeHelpers;
+import android.graphics.PixelFormat;
import android.util.Size;
/**
@@ -54,16 +56,17 @@ public final class StreamConfigurationDuration {
final int format, final int width, final int height, final long durationNs) {
mFormat = checkArgumentFormatInternal(format);
mWidth = checkArgumentPositive(width, "width must be positive");
- mHeight = checkArgumentPositive(width, "height must be positive");
+ mHeight = checkArgumentPositive(height, "height must be positive");
mDurationNs = checkArgumentNonnegative(durationNs, "durationNs must be non-negative");
}
/**
- * Get the image {@code format} in this stream configuration duration
+ * Get the internal image {@code format} in this stream configuration duration
*
* @return an integer format
*
* @see ImageFormat
+ * @see PixelFormat
*/
public final int getFormat() {
return mFormat;
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
new file mode 100644
index 0000000..4cd6d15
--- /dev/null
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -0,0 +1,949 @@
+/*
+ * 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.params;
+
+import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.utils.HashCodeHelpers;
+import android.view.Surface;
+import android.util.Log;
+import android.util.Size;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Objects;
+
+import static com.android.internal.util.Preconditions.*;
+
+/**
+ * Immutable class to store the available stream
+ * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP configurations} to be used
+ * when configuring streams with {@link CameraDevice#configureOutputs}.
+ * <!-- TODO: link to input stream configuration -->
+ *
+ * <p>This is the authoritative list for all <!-- input/ -->output formats (and sizes respectively
+ * for that format) that are supported by a camera device.</p>
+ *
+ * <p>This also contains the minimum frame durations and stall durations for each format/size
+ * combination that can be used to calculate effective frame rate when submitting multiple captures.
+ * </p>
+ *
+ * <p>An instance of this object is available from {@link CameraCharacteristics} using
+ * the {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP} key and the
+ * {@link CameraCharacteristics#get} method.</p>
+ *
+ * <pre><code>{@code
+ * CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
+ * StreamConfigurationMap configs = characteristics.get(
+ * CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ * }</code></pre>
+ *
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
+ * @see CameraDevice#configureOutputs
+ */
+public final class StreamConfigurationMap {
+
+ private static final String TAG = "StreamConfigurationMap";
+ /**
+ * Create a new {@link StreamConfigurationMap}.
+ *
+ * <p>The array parameters ownership is passed to this object after creation; do not
+ * write to them after this constructor is invoked.</p>
+ *
+ * @param configurations a non-{@code null} array of {@link StreamConfiguration}
+ * @param minFrameDurations a non-{@code null} array of {@link StreamConfigurationDuration}
+ * @param stallDurations a non-{@code null} array of {@link StreamConfigurationDuration}
+ *
+ * @throws NullPointerException if any of the arguments or subelements were {@code null}
+ *
+ * @hide
+ */
+ public StreamConfigurationMap(
+ StreamConfiguration[] configurations,
+ StreamConfigurationDuration[] minFrameDurations,
+ StreamConfigurationDuration[] stallDurations) {
+
+ mConfigurations = checkArrayElementsNotNull(configurations, "configurations");
+ mMinFrameDurations = checkArrayElementsNotNull(minFrameDurations, "minFrameDurations");
+ mStallDurations = checkArrayElementsNotNull(stallDurations, "stallDurations");
+
+ // For each format, track how many sizes there are available to configure
+ for (StreamConfiguration config : configurations) {
+ HashMap<Integer, Integer> map = config.isOutput() ? mOutputFormats : mInputFormats;
+
+ Integer count = map.get(config.getFormat());
+
+ if (count == null) {
+ count = 0;
+ }
+ count = count + 1;
+
+ map.put(config.getFormat(), count);
+ }
+
+ if (!mOutputFormats.containsKey(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED)) {
+ throw new AssertionError(
+ "At least one stream configuration for IMPLEMENTATION_DEFINED must exist");
+ }
+ }
+
+ /**
+ * Get the image {@code format} output formats in this stream configuration.
+ *
+ * <p>All image formats returned by this function will be defined in either {@link ImageFormat}
+ * or in {@link PixelFormat} (and there is no possibility of collision).</p>
+ *
+ * <p>Formats listed in this array are guaranteed to return true if queried with
+ * {@link #isOutputSupportedFor(int).</p>
+ *
+ * @return an array of integer format
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public final int[] getOutputFormats() {
+ return getPublicFormats(/*output*/true);
+ }
+
+ /**
+ * Get the image {@code format} input formats in this stream configuration.
+ *
+ * <p>All image formats returned by this function will be defined in either {@link ImageFormat}
+ * or in {@link PixelFormat} (and there is no possibility of collision).</p>
+ *
+ * @return an array of integer format
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ *
+ * @hide
+ */
+ public final int[] getInputFormats() {
+ return getPublicFormats(/*output*/false);
+ }
+
+ /**
+ * Get the supported input sizes for this input format.
+ *
+ * <p>The format must have come from {@link #getInputFormats}; otherwise
+ * {@code null} is returned.</p>
+ *
+ * @param format a format from {@link #getInputFormats}
+ * @return a non-empty array of sizes, or {@code null} if the format was not available.
+ *
+ * @hide
+ */
+ public Size[] getInputSizes(final int format) {
+ return getPublicFormatSizes(format, /*output*/false);
+ }
+
+ /**
+ * Determine whether or not output streams can be
+ * {@link CameraDevice#configureOutputs configured} with a particular user-defined format.
+ *
+ * <p>This method determines that the output {@code format} is supported by the camera device;
+ * each output {@code surface} target may or may not itself support that {@code format}.
+ * Refer to the class which provides the surface for additional documentation.</p>
+ *
+ * <p>Formats for which this returns {@code true} are guaranteed to exist in the result
+ * returned by {@link #getOutputSizes}.</p>
+ *
+ * @param format an image format from either {@link ImageFormat} or {@link PixelFormat}
+ * @return
+ * {@code true} iff using a {@code surface} with this {@code format} will be
+ * supported with {@link CameraDevice#configureOutputs}
+ *
+ * @throws IllegalArgumentException
+ * if the image format was not a defined named constant
+ * from either {@link ImageFormat} or {@link PixelFormat}
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ * @see CameraDevice#configureOutputs
+ */
+ public boolean isOutputSupportedFor(int format) {
+ checkArgumentFormat(format);
+
+ format = imageFormatToInternal(format);
+ return getFormatsMap(/*output*/true).containsKey(format);
+ }
+
+ /**
+ * Determine whether or not output streams can be configured with a particular class
+ * as a consumer.
+ *
+ * <p>The following list is generally usable for outputs:
+ * <ul>
+ * <li>{@link android.media.ImageReader} -
+ * Recommended for image processing or streaming to external resources (such as a file or
+ * network)
+ * <li>{@link android.media.MediaRecorder} -
+ * Recommended for recording video (simple to use)
+ * <li>{@link android.media.MediaCodec} -
+ * Recommended for recording video (more complicated to use, with more flexibility)
+ * <li>{@link android.renderscript.Allocation} -
+ * Recommended for image processing with {@link android.renderscript RenderScript}
+ * <li>{@link android.view.SurfaceHolder} -
+ * Recommended for low-power camera preview with {@link android.view.SurfaceView}
+ * <li>{@link android.graphics.SurfaceTexture} -
+ * Recommended for OpenGL-accelerated preview processing or compositing with
+ * {@link android.view.TextureView}
+ * </ul>
+ * </p>
+ *
+ * <p>Generally speaking this means that creating a {@link Surface} from that class <i>may</i>
+ * provide a producer endpoint that is suitable to be used with
+ * {@link CameraDevice#configureOutputs}.</p>
+ *
+ * <p>Since not all of the above classes support output of all format and size combinations,
+ * the particular combination should be queried with {@link #isOutputSupportedFor(Surface)}.</p>
+ *
+ * @param klass a non-{@code null} {@link Class} object reference
+ * @return {@code true} if this class is supported as an output, {@code false} otherwise
+ *
+ * @throws NullPointerException if {@code klass} was {@code null}
+ *
+ * @see CameraDevice#configureOutputs
+ * @see #isOutputSupportedFor(Surface)
+ */
+ public static <T> boolean isOutputSupportedFor(Class<T> klass) {
+ checkNotNull(klass, "klass must not be null");
+
+ if (klass == android.media.ImageReader.class) {
+ return true;
+ } else if (klass == android.media.MediaRecorder.class) {
+ return true;
+ } else if (klass == android.media.MediaCodec.class) {
+ return true;
+ } else if (klass == android.renderscript.Allocation.class) {
+ return true;
+ } else if (klass == android.view.SurfaceHolder.class) {
+ return true;
+ } else if (klass == android.graphics.SurfaceTexture.class) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Determine whether or not the {@code surface} in its current state is suitable to be
+ * {@link CameraDevice#configureOutputs configured} as an output.
+ *
+ * <p>Not all surfaces are usable with the {@link CameraDevice}, and not all configurations
+ * of that {@code surface} are compatible. Some classes that provide the {@code surface} are
+ * compatible with the {@link CameraDevice} in general
+ * (see {@link #isOutputSupportedFor(Class)}, but it is the caller's responsibility to put the
+ * {@code surface} into a state that will be compatible with the {@link CameraDevice}.</p>
+ *
+ * <p>Reasons for a {@code surface} being specifically incompatible might be:
+ * <ul>
+ * <li>Using a format that's not listed by {@link #getOutputFormats}
+ * <li>Using a format/size combination that's not listed by {@link #getOutputSizes}
+ * <li>The {@code surface} itself is not in a state where it can service a new producer.</p>
+ * </li>
+ * </ul>
+ *
+ * This is not an exhaustive list; see the particular class's documentation for further
+ * possible reasons of incompatibility.</p>
+ *
+ * @param surface a non-{@code null} {@link Surface} object reference
+ * @return {@code true} if this is supported, {@code false} otherwise
+ *
+ * @throws NullPointerException if {@code surface} was {@code null}
+ *
+ * @see CameraDevice#configureOutputs
+ * @see #isOutputSupportedFor(Class)
+ */
+ public boolean isOutputSupportedFor(Surface surface) {
+ checkNotNull(surface, "surface must not be null");
+
+ throw new UnsupportedOperationException("Not implemented yet");
+
+ // TODO: JNI function that checks the Surface's IGraphicBufferProducer state
+ }
+
+ /**
+ * Get a list of sizes compatible with {@code klass} to use as an output.
+ *
+ * <p>Since some of the supported classes may support additional formats beyond
+ * an opaque/implementation-defined (under-the-hood) format; this function only returns
+ * sizes for the implementation-defined format.</p>
+ *
+ * <p>Some classes such as {@link android.media.ImageReader} may only support user-defined
+ * formats; in particular {@link #isOutputSupportedFor(Class)} will return {@code true} for
+ * that class and this method will return an empty array (but not {@code null}).</p>
+ *
+ * <p>If a well-defined format such as {@code NV21} is required, use
+ * {@link #getOutputSizes(int)} instead.</p>
+ *
+ * <p>The {@code klass} should be a supported output, that querying
+ * {@code #isOutputSupportedFor(Class)} should return {@code true}.</p>
+ *
+ * @param klass
+ * a non-{@code null} {@link Class} object reference
+ * @return
+ * an array of supported sizes for implementation-defined formats,
+ * or {@code null} iff the {@code klass} is not a supported output
+ *
+ * @throws NullPointerException if {@code klass} was {@code null}
+ *
+ * @see #isOutputSupportedFor(Class)
+ */
+ public <T> Size[] getOutputSizes(Class<T> klass) {
+ if (isOutputSupportedFor(klass) == false) {
+ return null;
+ }
+
+ return getInternalFormatSizes(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, /*output*/true);
+ }
+
+ /**
+ * Get a list of sizes compatible with the requested image {@code format}.
+ *
+ * <p>The {@code format} should be a supported format (one of the formats returned by
+ * {@link #getOutputFormats}).</p>
+ *
+ * @param format an image format from {@link ImageFormat} or {@link PixelFormat}
+ * @return
+ * an array of supported sizes,
+ * or {@code null} if the {@code format} is not a supported output
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ * @see #getOutputFormats
+ */
+ public Size[] getOutputSizes(int format) {
+ return getPublicFormatSizes(format, /*output*/true);
+ }
+
+ /**
+ * Get the minimum {@link CaptureRequest#SENSOR_FRAME_DURATION frame duration}
+ * for the format/size combination (in nanoseconds).
+ *
+ * <p>{@code format} should be one of the ones returned by {@link #getOutputFormats()}.</p>
+ * <p>{@code size} should be one of the ones returned by
+ * {@link #getOutputSizes(int)}.</p>
+ *
+ * <p>This should correspond to the frame duration when only that stream is active, with all
+ * processing (typically in {@code android.*.mode}) set to either {@code OFF} or {@code FAST}.
+ * </p>
+ *
+ * <p>When multiple streams are used in a request, the minimum frame duration will be
+ * {@code max(individual stream min durations)}.</p>
+ *
+ * <!--
+ * TODO: uncomment after adding input stream support
+ * <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>
+ * -->
+ *
+ * @param format an image format from {@link ImageFormat} or {@link PixelFormat}
+ * @param size an output-compatible size
+ * @return a minimum frame duration {@code >=} 0 in nanoseconds
+ *
+ * @throws IllegalArgumentException if {@code format} or {@code size} was not supported
+ * @throws NullPointerException if {@code size} was {@code null}
+ *
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see #getOutputStallDuration(int, Size)
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public long getOutputMinFrameDuration(int format, Size size) {
+ checkNotNull(size, "size must not be null");
+ checkArgumentFormatSupported(format, /*output*/true);
+
+ return getInternalFormatDuration(imageFormatToInternal(format), size, DURATION_MIN_FRAME);
+ }
+
+ /**
+ * Get the minimum {@link CaptureRequest#SENSOR_FRAME_DURATION frame duration}
+ * for the class/size combination (in nanoseconds).
+ *
+ * <p>This assumes a the {@code klass} is set up to use an implementation-defined format.
+ * For user-defined formats, use {@link #getOutputMinFrameDuration(int, Size)}.</p>
+ *
+ * <p>{@code klass} should be one of the ones which is supported by
+ * {@link #isOutputSupportedFor(Class)}.</p>
+ *
+ * <p>{@code size} should be one of the ones returned by
+ * {@link #getOutputSizes(int)}.</p>
+ *
+ * <p>This should correspond to the frame duration when only that stream is active, with all
+ * processing (typically in {@code android.*.mode}) set to either {@code OFF} or {@code FAST}.
+ * </p>
+ *
+ * <p>When multiple streams are used in a request, the minimum frame duration will be
+ * {@code max(individual stream min durations)}.</p>
+ *
+ * <!--
+ * TODO: uncomment after adding input stream support
+ * <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>
+ * -->
+ *
+ * @param klass
+ * a class which is supported by {@link #isOutputSupportedFor(Class)} and has a
+ * non-empty array returned by {@link #getOutputSizes(Class)}
+ * @param size an output-compatible size
+ * @return a minimum frame duration {@code >=} 0 in nanoseconds
+ *
+ * @throws IllegalArgumentException if {@code klass} or {@code size} was not supported
+ * @throws NullPointerException if {@code size} or {@code klass} was {@code null}
+ *
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public <T> long getOutputMinFrameDuration(final Class<T> klass, final Size size) {
+ if (!isOutputSupportedFor(klass)) {
+ throw new IllegalArgumentException("klass was not supported");
+ }
+
+ return getInternalFormatDuration(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED,
+ size, DURATION_MIN_FRAME);
+ }
+
+ /**
+ * Get the stall duration for the format/size combination (in nanoseconds).
+ *
+ * <p>{@code format} should be one of the ones returned by {@link #getOutputFormats()}.</p>
+ * <p>{@code size} should be one of the ones returned by
+ * {@link #getOutputSizes(int)}.</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>For example, consider JPEG captures which have the following characteristics:
+ *
+ * <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>The JPEG processor can run concurrently to the rest of the camera pipeline, but cannot
+ * process more than 1 capture at a time.
+ * </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}) is the same as setting
+ * the minimum frame duration from the normal minimum frame duration corresponding to {@code S},
+ * added with the maximum stall duration for {@code S}.</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 {@code android.*.mode}) set to {@code FAST} or {@code OFF}.
+ * Setting any of the processing modes to {@code 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:
+ * <ul>
+ * <li>{@link ImageFormat#JPEG JPEG}
+ * <li>{@link ImageFormat#RAW_SENSOR RAW16}
+ * </ul>
+ * </p>
+ *
+ * <p>The following formats will never have a stall duration:
+ * <ul>
+ * <li>{@link ImageFormat#YUV_420_888 YUV_420_888}
+ * <li>{@link #isOutputSupportedFor(Class) Implementation-Defined}
+ * </ul></p>
+ *
+ * <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>
+ *
+ * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}
+ * for more information about calculating the max frame rate (absent stalls).</p>
+ *
+ * @param format an image format from {@link ImageFormat} or {@link PixelFormat}
+ * @param size an output-compatible size
+ * @return a stall duration {@code >=} 0 in nanoseconds
+ *
+ * @throws IllegalArgumentException if {@code format} or {@code size} was not supported
+ * @throws NullPointerException if {@code size} was {@code null}
+ *
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public long getOutputStallDuration(int format, Size size) {
+ checkArgumentFormatSupported(format, /*output*/true);
+
+ return getInternalFormatDuration(imageFormatToInternal(format),
+ size, DURATION_STALL);
+ }
+
+ /**
+ * Get the stall duration for the class/size combination (in nanoseconds).
+ *
+ * <p>This assumes a the {@code klass} is set up to use an implementation-defined format.
+ * For user-defined formats, use {@link #getOutputMinFrameDuration(int, Size)}.</p>
+ *
+ * <p>{@code klass} should be one of the ones with a non-empty array returned by
+ * {@link #getOutputSizes(Class)}.</p>
+ *
+ * <p>{@code size} should be one of the ones returned by
+ * {@link #getOutputSizes(Class)}.</p>
+ *
+ * <p>See {@link #getOutputStallDuration(int, Size)} for a definition of a
+ * <em>stall duration</em>.</p>
+ *
+ * @param klass
+ * a class which is supported by {@link #isOutputSupportedFor(Class)} and has a
+ * non-empty array returned by {@link #getOutputSizes(Class)}
+ * @param size an output-compatible size
+ * @return a minimum frame duration {@code >=} 0 in nanoseconds
+ *
+ * @throws IllegalArgumentException if {@code klass} or {@code size} was not supported
+ * @throws NullPointerException if {@code size} or {@code klass} was {@code null}
+ *
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public <T> long getOutputStallDuration(final Class<T> klass, final Size size) {
+ if (!isOutputSupportedFor(klass)) {
+ throw new IllegalArgumentException("klass was not supported");
+ }
+
+ return getInternalFormatDuration(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED,
+ size, DURATION_STALL);
+ }
+
+ /**
+ * Check if this {@link StreamConfigurationMap} is equal to another
+ * {@link StreamConfigurationMap}.
+ *
+ * <p>Two vectors are only equal if and only if each of the respective elements is equal.</p>
+ *
+ * @return {@code true} if the objects were equal, {@code false} otherwise
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof StreamConfigurationMap) {
+ final StreamConfigurationMap other = (StreamConfigurationMap) obj;
+ // XX: do we care about order?
+ return Arrays.equals(mConfigurations, other.mConfigurations) &&
+ Arrays.equals(mMinFrameDurations, other.mMinFrameDurations) &&
+ Arrays.equals(mStallDurations, other.mStallDurations);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ // XX: do we care about order?
+ return HashCodeHelpers.hashCode(mConfigurations, mMinFrameDurations, mStallDurations);
+ }
+
+ // Check that the argument is supported by #getOutputFormats or #getInputFormats
+ private int checkArgumentFormatSupported(int format, boolean output) {
+ checkArgumentFormat(format);
+
+ int[] formats = output ? getOutputFormats() : getInputFormats();
+ for (int i = 0; i < formats.length; ++i) {
+ if (format == formats[i]) {
+ return format;
+ }
+ }
+
+ throw new IllegalArgumentException(String.format(
+ "format %x is not supported by this stream configuration map", format));
+ }
+
+ /**
+ * Ensures that the format is either user-defined or implementation defined.
+ *
+ * <p>If a format has a different internal representation than the public representation,
+ * passing in the public representation here will fail.</p>
+ *
+ * <p>For example if trying to use {@link ImageFormat#JPEG}:
+ * it has a different public representation than the internal representation
+ * {@code HAL_PIXEL_FORMAT_BLOB}, this check will fail.</p>
+ *
+ * <p>Any invalid/undefined formats will raise an exception.</p>
+ *
+ * @param format image format
+ * @return the format
+ *
+ * @throws IllegalArgumentException if the format was invalid
+ */
+ static int checkArgumentFormatInternal(int format) {
+ switch (format) {
+ case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+ case HAL_PIXEL_FORMAT_BLOB:
+ return format;
+ case ImageFormat.JPEG:
+ throw new IllegalArgumentException(
+ "ImageFormat.JPEG is an unknown internal format");
+ default:
+ return checkArgumentFormat(format);
+ }
+ }
+
+ /**
+ * Ensures that the format is publicly user-defined in either ImageFormat or PixelFormat.
+ *
+ * <p>If a format has a different public representation than the internal representation,
+ * passing in the internal representation here will fail.</p>
+ *
+ * <p>For example if trying to use {@code HAL_PIXEL_FORMAT_BLOB}:
+ * it has a different internal representation than the public representation
+ * {@link ImageFormat#JPEG}, this check will fail.</p>
+ *
+ * <p>Any invalid/undefined formats will raise an exception, including implementation-defined.
+ * </p>
+ *
+ * <p>Note that {@code @hide} and deprecated formats will not pass this check.</p>
+ *
+ * @param format image format
+ * @return the format
+ *
+ * @throws IllegalArgumentException if the format was not user-defined
+ */
+ static int checkArgumentFormat(int format) {
+ // TODO: remove this hack , CTS shouldn't have been using internal constants
+ if (format == HAL_PIXEL_FORMAT_RAW_OPAQUE) {
+ Log.w(TAG, "RAW_OPAQUE is not yet a published format; allowing it anyway");
+ return format;
+ }
+
+ if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) {
+ throw new IllegalArgumentException(String.format(
+ "format 0x%x was not defined in either ImageFormat or PixelFormat", format));
+ }
+
+ return format;
+ }
+
+ /**
+ * Convert a public-visible {@code ImageFormat} into an internal format
+ * compatible with {@code graphics.h}.
+ *
+ * <p>In particular these formats are converted:
+ * <ul>
+ * <li>HAL_PIXEL_FORMAT_BLOB => ImageFormat.JPEG
+ * </ul>
+ * </p>
+ *
+ * <p>Passing in an implementation-defined format which has no public equivalent will fail;
+ * as will passing in a public format which has a different internal format equivalent.
+ * See {@link #checkArgumentFormat} for more details about a legal public format.</p>
+ *
+ * <p>All other formats are returned as-is, no further invalid check is performed.</p>
+ *
+ * <p>This function is the dual of {@link #imageFormatToInternal}.</p>
+ *
+ * @param format image format from {@link ImageFormat} or {@link PixelFormat}
+ * @return the converted image formats
+ *
+ * @throws IllegalArgumentException
+ * if {@code format} is {@code HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED} or
+ * {@link ImageFormat#JPEG}
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ * @see #checkArgumentFormat
+ */
+ static int imageFormatToPublic(int format) {
+ switch (format) {
+ case HAL_PIXEL_FORMAT_BLOB:
+ return ImageFormat.JPEG;
+ case ImageFormat.JPEG:
+ throw new IllegalArgumentException(
+ "ImageFormat.JPEG is an unknown internal format");
+ case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+ throw new IllegalArgumentException(
+ "IMPLEMENTATION_DEFINED must not leak to public API");
+ default:
+ return format;
+ }
+ }
+
+ /**
+ * Convert image formats from internal to public formats (in-place).
+ *
+ * @param formats an array of image formats
+ * @return {@code formats}
+ *
+ * @see #imageFormatToPublic
+ */
+ static int[] imageFormatToPublic(int[] formats) {
+ if (formats == null) {
+ return null;
+ }
+
+ for (int i = 0; i < formats.length; ++i) {
+ formats[i] = imageFormatToPublic(formats[i]);
+ }
+
+ return formats;
+ }
+
+ /**
+ * Convert a public format compatible with {@code ImageFormat} to an internal format
+ * from {@code graphics.h}.
+ *
+ * <p>In particular these formats are converted:
+ * <ul>
+ * <li>ImageFormat.JPEG => HAL_PIXEL_FORMAT_BLOB
+ * </ul>
+ * </p>
+ *
+ * <p>Passing in an implementation-defined format here will fail (it's not a public format);
+ * as will passing in an internal format which has a different public format equivalent.
+ * See {@link #checkArgumentFormat} for more details about a legal public format.</p>
+ *
+ * <p>All other formats are returned as-is, no invalid check is performed.</p>
+ *
+ * <p>This function is the dual of {@link #imageFormatToPublic}.</p>
+ *
+ * @param format public image format from {@link ImageFormat} or {@link PixelFormat}
+ * @return the converted image formats
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ *
+ * @throws IllegalArgumentException
+ * if {@code format} was {@code HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED}
+ */
+ static int imageFormatToInternal(int format) {
+ switch (format) {
+ case ImageFormat.JPEG:
+ return HAL_PIXEL_FORMAT_BLOB;
+ case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+ throw new IllegalArgumentException(
+ "IMPLEMENTATION_DEFINED is not allowed via public API");
+ default:
+ return format;
+ }
+ }
+
+ /**
+ * Convert image formats from public to internal formats (in-place).
+ *
+ * @param formats an array of image formats
+ * @return {@code formats}
+ *
+ * @see #imageFormatToInternal
+ *
+ * @hide
+ */
+ public static int[] imageFormatToInternal(int[] formats) {
+ if (formats == null) {
+ return null;
+ }
+
+ for (int i = 0; i < formats.length; ++i) {
+ formats[i] = imageFormatToInternal(formats[i]);
+ }
+
+ return formats;
+ }
+
+ private Size[] getPublicFormatSizes(int format, boolean output) {
+ try {
+ checkArgumentFormatSupported(format, output);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+
+ format = imageFormatToInternal(format);
+
+ return getInternalFormatSizes(format, output);
+ }
+
+ private Size[] getInternalFormatSizes(int format, boolean output) {
+ HashMap<Integer, Integer> formatsMap = getFormatsMap(output);
+
+ Integer sizesCount = formatsMap.get(format);
+ if (sizesCount == null) {
+ throw new IllegalArgumentException("format not available");
+ }
+
+ int len = sizesCount;
+ Size[] sizes = new Size[len];
+ int sizeIndex = 0;
+
+ for (StreamConfiguration config : mConfigurations) {
+ if (config.getFormat() == format && config.isOutput() == output) {
+ sizes[sizeIndex++] = config.getSize();
+ }
+ }
+
+ if (sizeIndex != len) {
+ throw new AssertionError(
+ "Too few sizes (expected " + len + ", actual " + sizeIndex + ")");
+ }
+
+ return sizes;
+ }
+
+ /** Get the list of publically visible output formats; does not include IMPL_DEFINED */
+ private int[] getPublicFormats(boolean output) {
+ int[] formats = new int[getPublicFormatCount(output)];
+
+ int i = 0;
+
+ for (int format : getFormatsMap(output).keySet()) {
+ if (format != HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
+ formats[i++] = format;
+ }
+ }
+
+ if (formats.length != i) {
+ throw new AssertionError("Too few formats " + i + ", expected " + formats.length);
+ }
+
+ return imageFormatToPublic(formats);
+ }
+
+ /** Get the format -> size count map for either output or input formats */
+ private HashMap<Integer, Integer> getFormatsMap(boolean output) {
+ return output ? mOutputFormats : mInputFormats;
+ }
+
+ private long getInternalFormatDuration(int format, Size size, int duration) {
+ // assume format is already checked, since its internal
+
+ if (!arrayContains(getInternalFormatSizes(format, /*output*/true), size)) {
+ throw new IllegalArgumentException("size was not supported");
+ }
+
+ StreamConfigurationDuration[] durations = getDurations(duration);
+
+ for (StreamConfigurationDuration configurationDuration : durations) {
+ if (configurationDuration.getFormat() == format &&
+ configurationDuration.getWidth() == size.getWidth() &&
+ configurationDuration.getHeight() == size.getHeight()) {
+ return configurationDuration.getDuration();
+ }
+ }
+
+ return getDurationDefault(duration);
+ }
+
+ /**
+ * Get the durations array for the kind of duration
+ *
+ * @see #DURATION_MIN_FRAME
+ * @see #DURATION_STALL
+ * */
+ private StreamConfigurationDuration[] getDurations(int duration) {
+ switch (duration) {
+ case DURATION_MIN_FRAME:
+ return mMinFrameDurations;
+ case DURATION_STALL:
+ return mStallDurations;
+ default:
+ throw new IllegalArgumentException("duration was invalid");
+ }
+ }
+
+ private long getDurationDefault(int duration) {
+ switch (duration) {
+ case DURATION_MIN_FRAME:
+ throw new AssertionError("Minimum frame durations are required to be listed");
+ case DURATION_STALL:
+ return 0L; // OK. A lack of a stall duration implies a 0 stall duration
+ default:
+ throw new IllegalArgumentException("duration was invalid");
+ }
+ }
+
+ /** Count the number of publicly-visible output formats */
+ private int getPublicFormatCount(boolean output) {
+ HashMap<Integer, Integer> formatsMap = getFormatsMap(output);
+
+ int size = formatsMap.size();
+ if (formatsMap.containsKey(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED)) {
+ size -= 1;
+ }
+ return size;
+ }
+
+ private static <T> boolean arrayContains(T[] array, T element) {
+ if (array == null) {
+ return false;
+ }
+
+ for (T el : array) {
+ if (Objects.equals(el, element)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // from system/core/include/system/graphics.h
+ private static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
+ private static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
+ private static final int HAL_PIXEL_FORMAT_RAW_OPAQUE = 0x24;
+
+ /**
+ * @see #getDurations(int)
+ * @see #getDurationDefault(int)
+ */
+ private static final int DURATION_MIN_FRAME = 0;
+ private static final int DURATION_STALL = 1;
+
+ private final StreamConfiguration[] mConfigurations;
+ private final StreamConfigurationDuration[] mMinFrameDurations;
+ private final StreamConfigurationDuration[] mStallDurations;
+
+ /** ImageFormat -> num output sizes mapping */
+ private final HashMap</*ImageFormat*/Integer, /*Count*/Integer> mOutputFormats =
+ new HashMap<Integer, Integer>();
+ /** ImageFormat -> num input sizes mapping */
+ private final HashMap</*ImageFormat*/Integer, /*Count*/Integer> mInputFormats =
+ new HashMap<Integer, Integer>();
+
+}
diff --git a/core/java/android/hardware/camera2/TonemapCurve.java b/core/java/android/hardware/camera2/params/TonemapCurve.java
index ee20d68..0fcffac 100644
--- a/core/java/android/hardware/camera2/TonemapCurve.java
+++ b/core/java/android/hardware/camera2/params/TonemapCurve.java
@@ -14,12 +14,16 @@
* limitations under the License.
*/
-package android.hardware.camera2;
+package android.hardware.camera2.params;
import static com.android.internal.util.Preconditions.*;
import android.graphics.PointF;
-import android.hardware.camera2.impl.HashCodeHelpers;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.utils.HashCodeHelpers;
import java.util.Arrays;
diff --git a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java
index 328ccbe..40cda08 100644
--- a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java
+++ b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java
@@ -40,6 +40,7 @@ public class CameraBinderDecorator {
public static final int ALREADY_EXISTS = -17;
public static final int BAD_VALUE = -22;
public static final int DEAD_OBJECT = -32;
+ public static final int INVALID_OPERATION = -38;
/**
* TODO: add as error codes in Errors.h
@@ -53,6 +54,7 @@ public class CameraBinderDecorator {
public static final int EOPNOTSUPP = -95;
public static final int EUSERS = -87;
+
private static class CameraBinderDecoratorListener implements Decorator.DecoratorListener {
@Override
@@ -125,6 +127,9 @@ public class CameraBinderDecorator {
case EOPNOTSUPP:
UncheckedThrow.throwAnyException(new CameraRuntimeException(
CAMERA_DEPRECATED_HAL));
+ case INVALID_OPERATION:
+ UncheckedThrow.throwAnyException(new IllegalStateException(
+ "Illegal state encountered in camera service."));
}
/**
diff --git a/core/java/android/hardware/camera2/impl/HashCodeHelpers.java b/core/java/android/hardware/camera2/utils/HashCodeHelpers.java
index 2d63827..b980549 100644
--- a/core/java/android/hardware/camera2/impl/HashCodeHelpers.java
+++ b/core/java/android/hardware/camera2/utils/HashCodeHelpers.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.hardware.camera2.impl;
+package android.hardware.camera2.utils;
/**
* Provide hashing functions using the Modified Bernstein hash
diff --git a/core/java/android/hardware/camera2/LongParcelable.aidl b/core/java/android/hardware/camera2/utils/LongParcelable.aidl
index 7d7e51b..98ad1b2 100644
--- a/core/java/android/hardware/camera2/LongParcelable.aidl
+++ b/core/java/android/hardware/camera2/utils/LongParcelable.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.hardware.camera2;
+package android.hardware.camera2.utils;
/** @hide */
-parcelable LongParcelable; \ No newline at end of file
+parcelable LongParcelable;
diff --git a/core/java/android/hardware/camera2/LongParcelable.java b/core/java/android/hardware/camera2/utils/LongParcelable.java
index 97b0631..c89b339 100644
--- a/core/java/android/hardware/camera2/LongParcelable.java
+++ b/core/java/android/hardware/camera2/utils/LongParcelable.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.hardware.camera2;
+package android.hardware.camera2.utils;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/hardware/camera2/utils/TypeReference.java b/core/java/android/hardware/camera2/utils/TypeReference.java
new file mode 100644
index 0000000..d0c919c
--- /dev/null
+++ b/core/java/android/hardware/camera2/utils/TypeReference.java
@@ -0,0 +1,435 @@
+/*
+ * 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.utils;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+
+import static com.android.internal.util.Preconditions.*;
+
+/**
+ * Super type token; allows capturing generic types at runtime by forcing them to be reified.
+ *
+ * <p>Usage example: <pre>{@code
+ * // using anonymous classes (preferred)
+ * TypeReference&lt;Integer> intToken = new TypeReference&lt;Integer>() {{ }};
+ *
+ * // using named classes
+ * class IntTypeReference extends TypeReference&lt;Integer> {...}
+ * TypeReference&lt;Integer> intToken = new IntTypeReference();
+ * }</p></pre>
+ *
+ * <p>Unlike the reference implementation, this bans nested TypeVariables; that is all
+ * dynamic types must equal to the static types.</p>
+ *
+ * <p>See <a href="http://gafter.blogspot.com/2007/05/limitation-of-super-type-tokens.html">
+ * http://gafter.blogspot.com/2007/05/limitation-of-super-type-tokens.html</a>
+ * for more details.</p>
+ */
+public abstract class TypeReference<T> {
+ private final Type mType;
+
+ /**
+ * Create a new type reference for {@code T}.
+ *
+ * @throws IllegalArgumentException if {@code T}'s actual type contains a type variable
+ *
+ * @see TypeReference
+ */
+ protected TypeReference() {
+ ParameterizedType thisType = (ParameterizedType)getClass().getGenericSuperclass();
+
+ // extract the "T" from TypeReference<T>
+ mType = thisType.getActualTypeArguments()[0];
+
+ /*
+ * Prohibit type references with type variables such as
+ *
+ * class GenericListToken<T> extends TypeReference<List<T>>
+ *
+ * Since the "T" there is not known without an instance of T, type equality would
+ * consider *all* Lists equal regardless of T. Allowing this would defeat
+ * some of the type safety of a type reference.
+ */
+ if (containsTypeVariable(mType)) {
+ throw new IllegalArgumentException(
+ "Including a type variable in a type reference is not allowed");
+ }
+ }
+
+ /**
+ * Return the dynamic {@link Type} corresponding to the captured type {@code T}.
+ */
+ public Type getType() {
+ return mType;
+ }
+
+ private TypeReference(Type type) {
+ mType = type;
+
+ if (containsTypeVariable(mType)) {
+ throw new IllegalArgumentException(
+ "Including a type variable in a type reference is not allowed");
+ }
+ }
+
+ private static class SpecializedTypeReference<T> extends TypeReference<T> {
+ public SpecializedTypeReference(Class<T> klass) {
+ super(klass);
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static class SpecializedBaseTypeReference extends TypeReference {
+ public SpecializedBaseTypeReference(Type type) {
+ super(type);
+ }
+ }
+
+ /**
+ * Create a specialized type reference from a dynamic class instance,
+ * bypassing the standard compile-time checks.
+ *
+ * <p>As with a regular type reference, the {@code klass} must not contain
+ * any type variables.</p>
+ *
+ * @param klass a non-{@code null} {@link Class} instance
+ *
+ * @return a type reference which captures {@code T} at runtime
+ *
+ * @throws IllegalArgumentException if {@code T} had any type variables
+ */
+ public static <T> TypeReference<T> createSpecializedTypeReference(Class<T> klass) {
+ return new SpecializedTypeReference<T>(klass);
+ }
+
+ /**
+ * Create a specialized type reference from a dynamic {@link Type} instance,
+ * bypassing the standard compile-time checks.
+ *
+ * <p>As with a regular type reference, the {@code type} must not contain
+ * any type variables.</p>
+ *
+ * @param type a non-{@code null} {@link Type} instance
+ *
+ * @return a type reference which captures {@code T} at runtime
+ *
+ * @throws IllegalArgumentException if {@code type} had any type variables
+ */
+ public static TypeReference<?> createSpecializedTypeReference(Type type) {
+ return new SpecializedBaseTypeReference(type);
+ }
+
+ /**
+ * Returns the raw type of T.
+ *
+ * <p><ul>
+ * <li>If T is a Class itself, T itself is returned.
+ * <li>If T is a ParameterizedType, the raw type of the parameterized type is returned.
+ * <li>If T is a GenericArrayType, the returned type is the corresponding array class.
+ * For example: {@code List<Integer>[]} => {@code List[]}.
+ * <li>If T is a type variable or a wildcard type, the raw type of the first upper bound is
+ * returned. For example: {@code <X extends Foo>} => {@code Foo}.
+ * </ul>
+ *
+ * @return the raw type of {@code T}
+ */
+ @SuppressWarnings("unchecked")
+ public final Class<? super T> getRawType() {
+ return (Class<? super T>)getRawType(mType);
+ }
+
+ private static final Class<?> getRawType(Type type) {
+ if (type == null) {
+ throw new NullPointerException("type must not be null");
+ }
+
+ if (type instanceof Class<?>) {
+ return (Class<?>)type;
+ } else if (type instanceof ParameterizedType) {
+ return (Class<?>)(((ParameterizedType)type).getRawType());
+ } else if (type instanceof GenericArrayType) {
+ return getArrayClass(getRawType(((GenericArrayType)type).getGenericComponentType()));
+ } else if (type instanceof WildcardType) {
+ // Should be at most 1 upper bound, but treat it like an array for simplicity
+ return getRawType(((WildcardType) type).getUpperBounds());
+ } else if (type instanceof TypeVariable) {
+ throw new AssertionError("Type variables are not allowed in type references");
+ } else {
+ // Impossible
+ throw new AssertionError("Unhandled branch to get raw type for type " + type);
+ }
+ }
+
+ private static final Class<?> getRawType(Type[] types) {
+ if (types == null) {
+ return null;
+ }
+
+ for (Type type : types) {
+ Class<?> klass = getRawType(type);
+ if (klass != null) {
+ return klass;
+ }
+ }
+
+ return null;
+ }
+
+ private static final Class<?> getArrayClass(Class<?> componentType) {
+ return Array.newInstance(componentType, 0).getClass();
+ }
+
+ /**
+ * Get the component type, e.g. {@code T} from {@code T[]}.
+ *
+ * @return component type, or {@code null} if {@code T} is not an array
+ */
+ public TypeReference<?> getComponentType() {
+ Type componentType = getComponentType(mType);
+
+ return (componentType != null) ?
+ createSpecializedTypeReference(componentType) :
+ null;
+ }
+
+ private static Type getComponentType(Type type) {
+ checkNotNull(type, "type must not be null");
+
+ if (type instanceof Class<?>) {
+ return ((Class<?>) type).getComponentType();
+ } else if (type instanceof ParameterizedType) {
+ return null;
+ } else if (type instanceof GenericArrayType) {
+ return ((GenericArrayType)type).getGenericComponentType();
+ } else if (type instanceof WildcardType) {
+ // Should be at most 1 upper bound, but treat it like an array for simplicity
+ throw new UnsupportedOperationException("TODO: support wild card components");
+ } else if (type instanceof TypeVariable) {
+ throw new AssertionError("Type variables are not allowed in type references");
+ } else {
+ // Impossible
+ throw new AssertionError("Unhandled branch to get component type for type " + type);
+ }
+ }
+
+ /**
+ * Compare two objects for equality.
+ *
+ * <p>A TypeReference is only equal to another TypeReference if their captured type {@code T}
+ * is also equal.</p>
+ */
+ @Override
+ public boolean equals(Object o) {
+ // Note that this comparison could inaccurately return true when comparing types
+ // with nested type variables; therefore we ban type variables in the constructor.
+ return o instanceof TypeReference<?> && mType.equals(((TypeReference<?>)o).mType);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return mType.hashCode();
+ }
+
+ /**
+ * Check if the {@code type} contains a {@link TypeVariable} recursively.
+ *
+ * <p>Intuitively, a type variable is a type in a type expression that refers to a generic
+ * type which is not known at the definition of the expression (commonly seen when
+ * type parameters are used, e.g. {@code class Foo<T>}).</p>
+ *
+ * <p>See <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.4">
+ * http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.4</a>
+ * for a more formal definition of a type variable</p>.
+ *
+ * @param type a type object ({@code null} is allowed)
+ * @return {@code true} if there were nested type variables; {@code false} otherwise
+ */
+ public static boolean containsTypeVariable(Type type) {
+ if (type == null) {
+ // Trivially false
+ return false;
+ } else if (type instanceof TypeVariable<?>) {
+ /*
+ * T -> trivially true
+ */
+ return true;
+ } else if (type instanceof Class<?>) {
+ /*
+ * class Foo -> no type variable
+ * class Foo<T> - has a type variable
+ *
+ * This also covers the case of class Foo<T> extends ... / implements ...
+ * since everything on the right hand side would either include a type variable T
+ * or have no type variables.
+ */
+ Class<?> klass = (Class<?>)type;
+
+ // Empty array => class is not generic
+ if (klass.getTypeParameters().length != 0) {
+ return true;
+ } else {
+ // Does the outer class(es) contain any type variables?
+
+ /*
+ * class Outer<T> {
+ * class Inner {
+ * T field;
+ * }
+ * }
+ *
+ * In this case 'Inner' has no type parameters itself, but it still has a type
+ * variable as part of the type definition.
+ */
+ return containsTypeVariable(klass.getDeclaringClass());
+ }
+ } else if (type instanceof ParameterizedType) {
+ /*
+ * This is the "Foo<T1, T2, T3, ... Tn>" in the scope of a
+ *
+ * // no type variables here, T1-Tn are known at this definition
+ * class X extends Foo<T1, T2, T3, ... Tn>
+ *
+ * // T1 is a type variable, T2-Tn are known at this definition
+ * class X<T1> extends Foo<T1, T2, T3, ... Tn>
+ */
+ ParameterizedType p = (ParameterizedType) type;
+
+ // This needs to be recursively checked
+ for (Type arg : p.getActualTypeArguments()) {
+ if (containsTypeVariable(arg)) {
+ return true;
+ }
+ }
+
+ return false;
+ } else if (type instanceof WildcardType) {
+ WildcardType wild = (WildcardType) type;
+
+ /*
+ * This is is the "?" inside of a
+ *
+ * Foo<?> --> unbounded; trivially no type variables
+ * Foo<? super T> --> lower bound; does T have a type variable?
+ * Foo<? extends T> --> upper bound; does T have a type variable?
+ */
+
+ /*
+ * According to JLS 4.5.1
+ * (http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.5.1):
+ *
+ * - More than 1 lower/upper bound is illegal
+ * - Both a lower and upper bound is illegal
+ *
+ * However, we use this 'array OR array' approach for readability
+ */
+ return containsTypeVariable(wild.getLowerBounds()) ||
+ containsTypeVariable(wild.getUpperBounds());
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("TypeReference<");
+ toString(getType(), builder);
+ builder.append(">");
+
+ return builder.toString();
+ }
+
+ private static void toString(Type type, StringBuilder out) {
+ if (type == null) {
+ return;
+ } else if (type instanceof TypeVariable<?>) {
+ // T
+ out.append(((TypeVariable<?>)type).getName());
+ } else if (type instanceof Class<?>) {
+ Class<?> klass = (Class<?>)type;
+
+ out.append(klass.getName());
+ toString(klass.getTypeParameters(), out);
+ } else if (type instanceof ParameterizedType) {
+ // "Foo<T1, T2, T3, ... Tn>"
+ ParameterizedType p = (ParameterizedType) type;
+
+ out.append(((Class<?>)p.getRawType()).getName());
+ toString(p.getActualTypeArguments(), out);
+ } else if (type instanceof GenericArrayType) {
+ GenericArrayType gat = (GenericArrayType)type;
+
+ toString(gat.getGenericComponentType(), out);
+ out.append("[]");
+ } else { // WildcardType, BoundedType
+ // TODO:
+ out.append(type.toString());
+ }
+ }
+
+ private static void toString(Type[] types, StringBuilder out) {
+ if (types == null) {
+ return;
+ } else if (types.length == 0) {
+ return;
+ }
+
+ out.append("<");
+
+ for (int i = 0; i < types.length; ++i) {
+ toString(types[i], out);
+ if (i != types.length - 1) {
+ out.append(", ");
+ }
+ }
+
+ out.append(">");
+ }
+
+ /**
+ * Check if any of the elements in this array contained a type variable.
+ *
+ * <p>Empty and null arrays trivially have no type variables.</p>
+ *
+ * @param typeArray an array ({@code null} is ok) of types
+ * @return true if any elements contained a type variable; false otherwise
+ */
+ private static boolean containsTypeVariable(Type[] typeArray) {
+ if (typeArray == null) {
+ return false;
+ }
+
+ for (Type type : typeArray) {
+ if (containsTypeVariable(type)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index cec90cd..e58c54d 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -156,6 +156,9 @@ public abstract class DisplayManagerInternal {
// If true, enables automatic brightness control.
public boolean useAutoBrightness;
+ //If true, scales the brightness to half of desired.
+ public boolean lowPowerMode;
+
// If true, prevents the screen from completely turning on if it is currently off.
// The display does not enter a "ready" state if this flag is true and screen on is
// blocked. The window manager policy blocks screen on while it prepares the keyguard to
@@ -203,6 +206,7 @@ public abstract class DisplayManagerInternal {
screenAutoBrightnessAdjustment = other.screenAutoBrightnessAdjustment;
useAutoBrightness = other.useAutoBrightness;
blockScreenOn = other.blockScreenOn;
+ lowPowerMode = other.lowPowerMode;
}
@Override
@@ -218,7 +222,8 @@ public abstract class DisplayManagerInternal {
&& screenBrightness == other.screenBrightness
&& screenAutoBrightnessAdjustment == other.screenAutoBrightnessAdjustment
&& useAutoBrightness == other.useAutoBrightness
- && blockScreenOn == other.blockScreenOn;
+ && blockScreenOn == other.blockScreenOn
+ && lowPowerMode == other.lowPowerMode;
}
@Override
@@ -233,7 +238,8 @@ public abstract class DisplayManagerInternal {
+ ", screenBrightness=" + screenBrightness
+ ", screenAutoBrightnessAdjustment=" + screenAutoBrightnessAdjustment
+ ", useAutoBrightness=" + useAutoBrightness
- + ", blockScreenOn=" + blockScreenOn;
+ + ", blockScreenOn=" + blockScreenOn
+ + ", lowPowerMode=" + lowPowerMode;
}
}
diff --git a/core/java/android/hardware/hdmi/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java
index 7213c78..723eda1 100644
--- a/core/java/android/hardware/hdmi/HdmiCec.java
+++ b/core/java/android/hardware/hdmi/HdmiCec.java
@@ -120,7 +120,7 @@ public final class HdmiCec {
public static final int MESSAGE_TIMER_CLEARED_STATUS = 0x043;
public static final int MESSAGE_USER_CONTROL_PRESSED = 0x44;
public static final int MESSAGE_USER_CONTROL_RELEASED = 0x45;
- public static final int MESSAGE_GET_OSD_NAME = 0x46;
+ public static final int MESSAGE_GIVE_OSD_NAME = 0x46;
public static final int MESSAGE_SET_OSD_NAME = 0x47;
public static final int MESSAGE_SET_OSD_STRING = 0x64;
public static final int MESSAGE_SET_TIMER_PROGRAM_TITLE = 0x67;
@@ -158,6 +158,12 @@ public final class HdmiCec {
public static final int MESSAGE_VENDOR_COMMAND_WITH_ID = 0xA0;
public static final int MESSAGE_CLEAR_EXTERNAL_TIMER = 0xA1;
public static final int MESSAGE_SET_EXTERNAL_TIMER = 0xA2;
+ public static final int MESSAGE_INITIATE_ARC = 0xC0;
+ public static final int MESSAGE_REPORT_ARC_INITIATED = 0xC1;
+ public static final int MESSAGE_REPORT_ARC_TERMINATED = 0xC2;
+ public static final int MESSAGE_REQUEST_ARC_INITIATION = 0xC3;
+ public static final int MESSAGE_REQUEST_ARC_TERMINATION = 0xC4;
+ public static final int MESSAGE_TERMINATE_ARC = 0xC5;
public static final int MESSAGE_ABORT = 0xFF;
public static final int UNKNOWN_VENDOR_ID = 0xFFFFFF;
@@ -165,8 +171,15 @@ public final class HdmiCec {
public static final int POWER_STATUS_UNKNOWN = -1;
public static final int POWER_STATUS_ON = 0;
public static final int POWER_STATUS_STANDBY = 1;
- public static final int POWER_TRANSIENT_TO_ON = 2;
- public static final int POWER_TRANSIENT_TO_STANDBY = 3;
+ public static final int POWER_STATUS_TRANSIENT_TO_ON = 2;
+ public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3;
+
+ public static final int RESULT_SUCCESS = 0;
+ public static final int RESULT_TIMEOUT = 1;
+ public static final int RESULT_SOURCE_NOT_AVAILABLE = 2;
+ public static final int RESULT_TARGET_NOT_AVAILABLE = 3;
+ public static final int RESULT_ALREADY_IN_PROGRESS = 4;
+ public static final int RESULT_EXCEPTION = 5;
private static final int[] ADDRESS_TO_TYPE = {
DEVICE_TV, // ADDR_TV
@@ -181,6 +194,8 @@ public final class HdmiCec {
DEVICE_RECORDER, // ADDR_RECORDER_3
DEVICE_TUNER, // ADDR_TUNER_4
DEVICE_PLAYBACK, // ADDR_PLAYBACK_3
+ DEVICE_RESERVED,
+ DEVICE_RESERVED,
DEVICE_TV, // ADDR_SPECIFIC_USE
};
@@ -197,6 +212,8 @@ public final class HdmiCec {
"Recorder_3",
"Tuner_4",
"Playback_3",
+ "Reserved_1",
+ "Reserved_2",
"Secondary_TV",
};
diff --git a/core/java/android/hardware/hdmi/HdmiCecClient.java b/core/java/android/hardware/hdmi/HdmiCecClient.java
index cd86cd8..dcb3624 100644
--- a/core/java/android/hardware/hdmi/HdmiCecClient.java
+++ b/core/java/android/hardware/hdmi/HdmiCecClient.java
@@ -69,44 +69,28 @@ public final class HdmiCecClient {
* Send &lt;Active Source&gt; message.
*/
public void sendActiveSource() {
- try {
- mService.sendActiveSource(mBinder);
- } catch (RemoteException e) {
- Log.e(TAG, "sendActiveSource threw exception ", e);
- }
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
}
/**
* Send &lt;Inactive Source&gt; message.
*/
public void sendInactiveSource() {
- try {
- mService.sendInactiveSource(mBinder);
- } catch (RemoteException e) {
- Log.e(TAG, "sendInactiveSource threw exception ", e);
- }
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
}
/**
* Send &lt;Text View On&gt; message.
*/
public void sendTextViewOn() {
- try {
- mService.sendTextViewOn(mBinder);
- } catch (RemoteException e) {
- Log.e(TAG, "sendTextViewOn threw exception ", e);
- }
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
}
/**
* Send &lt;Image View On&gt; message.
*/
public void sendImageViewOn() {
- try {
- mService.sendImageViewOn(mBinder);
- } catch (RemoteException e) {
- Log.e(TAG, "sendImageViewOn threw exception ", e);
- }
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
}
/**
@@ -116,11 +100,7 @@ public final class HdmiCecClient {
* {@link HdmiCec#ADDR_TV}.
*/
public void sendGiveDevicePowerStatus(int address) {
- try {
- mService.sendGiveDevicePowerStatus(mBinder, address);
- } catch (RemoteException e) {
- Log.e(TAG, "sendGiveDevicePowerStatus threw exception ", e);
- }
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
}
/**
@@ -133,11 +113,7 @@ public final class HdmiCecClient {
* @return true if TV is on; otherwise false.
*/
public boolean isTvOn() {
- try {
- return mService.isTvOn(mBinder);
- } catch (RemoteException e) {
- Log.e(TAG, "isTvOn threw exception ", e);
- }
- return false;
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
+ return true;
}
}
diff --git a/core/java/android/hardware/hdmi/HdmiCecManager.java b/core/java/android/hardware/hdmi/HdmiCecManager.java
index 10b058c..03c46d8 100644
--- a/core/java/android/hardware/hdmi/HdmiCecManager.java
+++ b/core/java/android/hardware/hdmi/HdmiCecManager.java
@@ -45,15 +45,7 @@ public final class HdmiCecManager {
* @return {@link HdmiCecClient} instance. {@code null} on failure.
*/
public HdmiCecClient getClient(int type, HdmiCecClient.Listener listener) {
- if (mService == null) {
- return null;
- }
- try {
- IBinder b = mService.allocateLogicalDevice(type, getListenerWrapper(listener));
- return HdmiCecClient.create(mService, b);
- } catch (RemoteException e) {
- return null;
- }
+ return HdmiCecClient.create(mService, null);
}
private IHdmiCecListener getListenerWrapper(final HdmiCecClient.Listener listener) {
diff --git a/core/java/android/hardware/hdmi/HdmiCecMessage.java b/core/java/android/hardware/hdmi/HdmiCecMessage.java
index ddaf870..62fa279 100644
--- a/core/java/android/hardware/hdmi/HdmiCecMessage.java
+++ b/core/java/android/hardware/hdmi/HdmiCecMessage.java
@@ -46,7 +46,7 @@ public final class HdmiCecMessage implements Parcelable {
public HdmiCecMessage(int source, int destination, int opcode, byte[] params) {
mSource = source;
mDestination = destination;
- mOpcode = opcode;
+ mOpcode = opcode & 0xFF;
mParams = Arrays.copyOf(params, params.length);
}
@@ -123,6 +123,7 @@ public final class HdmiCecMessage implements Parcelable {
* @param p HdmiCecMessage object to read the Rating from
* @return a new HdmiCecMessage created from the data in the parcel
*/
+ @Override
public HdmiCecMessage createFromParcel(Parcel p) {
int source = p.readInt();
int destination = p.readInt();
@@ -131,6 +132,7 @@ public final class HdmiCecMessage implements Parcelable {
p.readByteArray(params);
return new HdmiCecMessage(source, destination, opcode, params);
}
+ @Override
public HdmiCecMessage[] newArray(int size) {
return new HdmiCecMessage[size];
}
@@ -139,11 +141,40 @@ public final class HdmiCecMessage implements Parcelable {
@Override
public String toString() {
StringBuffer s = new StringBuffer();
- s.append(String.format("src: %d dst: %d op: %2X params: ", mSource, mDestination, mOpcode));
- for (byte data : mParams) {
- s.append(String.format("%02X ", data));
+ s.append(String.format("<%s> src: %d, dst: %d",
+ opcodeToString(mOpcode), mSource, mDestination));
+ if (mParams.length > 0) {
+ s.append(", params:");
+ for (byte data : mParams) {
+ s.append(String.format(" %02X", data));
+ }
}
return s.toString();
}
+
+ private static String opcodeToString(int opcode) {
+ switch (opcode) {
+ case HdmiCec.MESSAGE_FEATURE_ABORT:
+ return "Feature Abort";
+ case HdmiCec.MESSAGE_CEC_VERSION:
+ return "CEC Version";
+ case HdmiCec.MESSAGE_REQUEST_ARC_INITIATION:
+ return "Request ARC Initiation";
+ case HdmiCec.MESSAGE_REQUEST_ARC_TERMINATION:
+ return "Request ARC Termination";
+ case HdmiCec.MESSAGE_REPORT_ARC_INITIATED:
+ return "Report ARC Initiated";
+ case HdmiCec.MESSAGE_REPORT_ARC_TERMINATED:
+ return "Report ARC Terminated";
+ case HdmiCec.MESSAGE_TEXT_VIEW_ON:
+ return "Text View On";
+ case HdmiCec.MESSAGE_ACTIVE_SOURCE:
+ return "Active Source";
+ case HdmiCec.MESSAGE_GIVE_DEVICE_POWER_STATUS:
+ return "Give Device Power Status";
+ default:
+ return String.format("Opcode: %02X", opcode);
+ }
+ }
}
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
new file mode 100644
index 0000000..5b6e862
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -0,0 +1,157 @@
+/*
+ * 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.annotation.Nullable;
+import android.os.RemoteException;
+
+/**
+ * The {@link HdmiControlManager} class is used to send HDMI control messages
+ * to attached CEC devices.
+ *
+ * <p>Provides various HDMI client instances that represent HDMI-CEC logical devices
+ * hosted in the system. {@link #getTvClient()}, for instance will return an
+ * {@link HdmiTvClient} object if the system is configured to host one. Android system
+ * can host more than one logical CEC devices. If multiple types are configured they
+ * all work as if they were independent logical devices running in the system.
+ */
+public final class HdmiControlManager {
+ @Nullable private final IHdmiControlService mService;
+
+ // True if we have a logical device of type playback hosted in the system.
+ private final boolean mHasPlaybackDevice;
+ // True if we have a logical device of type TV hosted in the system.
+ private final boolean mHasTvDevice;
+
+ /**
+ * @hide - hide this constructor because it has a parameter of type
+ * IHdmiControlService, which is a system private class. The right way
+ * to create an instance of this class is using the factory
+ * Context.getSystemService.
+ */
+ public HdmiControlManager(IHdmiControlService service) {
+ mService = service;
+ int[] types = null;
+ if (mService != null) {
+ try {
+ types = mService.getSupportedTypes();
+ } catch (RemoteException e) {
+ // Do nothing.
+ }
+ }
+ mHasTvDevice = hasDeviceType(types, HdmiCec.DEVICE_TV);
+ mHasPlaybackDevice = hasDeviceType(types, HdmiCec.DEVICE_PLAYBACK);
+ }
+
+ private static boolean hasDeviceType(int[] types, int type) {
+ if (types == null) {
+ return false;
+ }
+ for (int t : types) {
+ if (t == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets an object that represents a HDMI-CEC logical device of type playback on the system.
+ *
+ * <p>Used to send HDMI control messages to other devices like TV or audio amplifier through
+ * HDMI bus. It is also possible to communicate with other logical devices hosted in the same
+ * system if the system is configured to host more than one type of HDMI-CEC logical devices.
+ *
+ * @return {@link HdmiPlaybackClient} instance. {@code null} on failure.
+ */
+ @Nullable
+ public HdmiPlaybackClient getPlaybackClient() {
+ if (mService == null || !mHasPlaybackDevice) {
+ return null;
+ }
+ return new HdmiPlaybackClient(mService);
+ }
+
+ /**
+ * Gets an object that represents a HDMI-CEC logical device of type TV on the system.
+ *
+ * <p>Used to send HDMI control messages to other devices and manage them through
+ * HDMI bus. It is also possible to communicate with other logical devices hosted in the same
+ * system if the system is configured to host more than one type of HDMI-CEC logical devices.
+ *
+ * @return {@link HdmiTvClient} instance. {@code null} on failure.
+ */
+ @Nullable
+ public HdmiTvClient getTvClient() {
+ if (mService == null || !mHasTvDevice) {
+ return null;
+ }
+ return new HdmiTvClient(mService);
+ }
+
+ /**
+ * Listener used to get hotplug event from HDMI port.
+ */
+ public interface HotplugEventListener {
+ void onReceived(HdmiHotplugEvent event);
+ }
+
+ /**
+ * Adds a listener to get informed of {@link HdmiHotplugEvent}.
+ *
+ * <p>To stop getting the notification,
+ * use {@link #removeHotplugEventListener(HotplugEventListener)}.
+ *
+ * @param listener {@link HotplugEventListener} instance
+ * @see HdmiControlManager#removeHotplugEventListener(HotplugEventListener)
+ */
+ public void addHotplugEventListener(HotplugEventListener listener) {
+ if (mService == null) {
+ return;
+ }
+ try {
+ mService.addHotplugEventListener(getHotplugEventListenerWrapper(listener));
+ } catch (RemoteException e) {
+ // Do nothing.
+ }
+ }
+
+ /**
+ * Removes a listener to stop getting informed of {@link HdmiHotplugEvent}.
+ *
+ * @param listener {@link HotplugEventListener} instance to be removed
+ */
+ public void removeHotplugEventListener(HotplugEventListener listener) {
+ if (mService == null) {
+ return;
+ }
+ try {
+ mService.removeHotplugEventListener(getHotplugEventListenerWrapper(listener));
+ } catch (RemoteException e) {
+ // Do nothing.
+ }
+ }
+
+ private IHdmiHotplugEventListener getHotplugEventListenerWrapper(
+ final HotplugEventListener listener) {
+ return new IHdmiHotplugEventListener.Stub() {
+ public void onReceived(HdmiHotplugEvent event) {
+ listener.onReceived(event);;
+ }
+ };
+ }
+}
diff --git a/core/java/android/hardware/hdmi/HdmiHotplugEvent.aidl b/core/java/android/hardware/hdmi/HdmiHotplugEvent.aidl
new file mode 100644
index 0000000..3117dd6
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiHotplugEvent.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.hdmi;
+
+parcelable HdmiHotplugEvent;
diff --git a/core/java/android/hardware/hdmi/HdmiHotplugEvent.java b/core/java/android/hardware/hdmi/HdmiHotplugEvent.java
new file mode 100644
index 0000000..1462f83
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiHotplugEvent.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.hdmi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that describes the HDMI port hotplug event.
+ */
+public final class HdmiHotplugEvent implements Parcelable {
+
+ private final int mPort;
+ private final boolean mConnected;
+
+ /**
+ * Constructor.
+ *
+ * <p>Marked as hidden so only system can create the instance.
+ *
+ * @hide
+ */
+ public HdmiHotplugEvent(int port, boolean connected) {
+ mPort = port;
+ mConnected = connected;
+ }
+
+ /**
+ * Return the port number for which the event occurred.
+ *
+ * @return port number
+ */
+ public int getPort() {
+ return mPort;
+ }
+
+ /**
+ * Return the connection status associated with this event
+ *
+ * @return true if the device gets connected; otherwise false
+ */
+ public boolean isConnected() {
+ return mConnected;
+ }
+
+ /**
+ * Describe the kinds of special objects contained in this Parcelable's
+ * marshalled representation.
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Flatten this object in to a 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(mPort);
+ dest.writeByte((byte) (mConnected ? 1 : 0));
+ }
+
+ public static final Parcelable.Creator<HdmiHotplugEvent> CREATOR
+ = new Parcelable.Creator<HdmiHotplugEvent>() {
+ /**
+ * Rebuild a {@link HdmiHotplugEvent} previously stored with
+ * {@link Parcelable#writeToParcel(Parcel, int)}.
+ *
+ * @param p {@link HdmiHotplugEvent} object to read the Rating from
+ * @return a new {@link HdmiHotplugEvent} created from the data in the parcel
+ */
+ public HdmiHotplugEvent createFromParcel(Parcel p) {
+ int port = p.readInt();
+ boolean connected = p.readByte() == 1;
+ return new HdmiHotplugEvent(port, connected);
+ }
+ public HdmiHotplugEvent[] newArray(int size) {
+ return new HdmiHotplugEvent[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
new file mode 100644
index 0000000..f0bd237
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiPlaybackClient.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 android.hardware.hdmi;
+
+import android.os.RemoteException;
+
+import android.util.Log;
+
+/**
+ * HdmiPlaybackClient represents HDMI-CEC logical device of type Playback
+ * in the Android system which acts as a playback device such as set-top box.
+ * It provides with methods that control, get information from TV/Display device
+ * connected through HDMI bus.
+ */
+public final class HdmiPlaybackClient {
+ private static final String TAG = "HdmiPlaybackClient";
+
+ private final IHdmiControlService mService;
+
+ /**
+ * Listener used by the client to get the result of one touch play operation.
+ */
+ public interface OneTouchPlayCallback {
+ /**
+ * Called when the result of the feature one touch play is returned.
+ *
+ * @param result the result of the operation. {@link HdmiCec#RESULT_SUCCESS}
+ * if successful.
+ */
+ public void onComplete(int result);
+ }
+
+ /**
+ * Listener used by the client to get display device status.
+ */
+ public interface DisplayStatusCallback {
+ /**
+ * Called when display device status is reported.
+ *
+ * @param status display device status
+ * @see {@link HdmiCec#POWER_STATUS_ON}
+ * @see {@link HdmiCec#POWER_STATUS_STANDBY}
+ * @see {@link HdmiCec#POWER_STATUS_TRANSIENT_TO_ON}
+ * @see {@link HdmiCec#POWER_STATUS_TRANSIENT_TO_STANDBY}
+ * @see {@link HdmiCec#POWER_STATUS_UNKNOWN}
+ */
+ public void onComplete(int status);
+ }
+
+ HdmiPlaybackClient(IHdmiControlService service) {
+ mService = service;
+ }
+
+ /**
+ * Perform the feature 'one touch play' from playback device to turn on display
+ * and switch the input.
+ *
+ * @param callback {@link OneTouchPlayCallback} object to get informed
+ * of the result
+ */
+ public void oneTouchPlay(OneTouchPlayCallback callback) {
+ // TODO: Use PendingResult.
+ try {
+ mService.oneTouchPlay(getCallbackWrapper(callback));
+ } catch (RemoteException e) {
+ Log.e(TAG, "oneTouchPlay threw exception ", e);
+ }
+ }
+
+ /**
+ * Get the status of display device connected through HDMI bus.
+ *
+ * @param callback {@link DisplayStatusCallback} object to get informed
+ * of the result
+ */
+ public void queryDisplayStatus(DisplayStatusCallback callback) {
+ // TODO: PendingResult.
+ try {
+ mService.queryDisplayStatus(getCallbackWrapper(callback));
+ } catch (RemoteException e) {
+ Log.e(TAG, "queryDisplayStatus threw exception ", e);
+ }
+ }
+
+ private IHdmiControlCallback getCallbackWrapper(final OneTouchPlayCallback callback) {
+ return new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ callback.onComplete(result);
+ }
+ };
+ }
+
+ private IHdmiControlCallback getCallbackWrapper(final DisplayStatusCallback callback) {
+ return new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int status) {
+ callback.onComplete(status);
+ }
+ };
+ }
+}
diff --git a/core/java/android/hardware/hdmi/HdmiTvClient.java b/core/java/android/hardware/hdmi/HdmiTvClient.java
new file mode 100644
index 0000000..73c7247
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiTvClient.java
@@ -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.hardware.hdmi;
+
+/**
+ * HdmiTvClient represents HDMI-CEC logical device of type TV in the Android system
+ * which acts as TV/Display. It provides with methods that manage, interact with other
+ * devices on the CEC bus.
+ */
+public final class HdmiTvClient {
+ private static final String TAG = "HdmiTvClient";
+
+ private final IHdmiControlService mService;
+
+ HdmiTvClient(IHdmiControlService service) {
+ mService = service;
+ }
+}
diff --git a/core/java/android/tv/ITvInputSessionCallback.aidl b/core/java/android/hardware/hdmi/IHdmiControlCallback.aidl
index a2bd0d7..ef3dd47 100644
--- a/core/java/android/tv/ITvInputSessionCallback.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlCallback.aidl
@@ -14,15 +14,14 @@
* limitations under the License.
*/
-package android.tv;
-
-import android.tv.ITvInputSession;
+package android.hardware.hdmi;
/**
- * Helper interface for ITvInputSession to allow the TV input to notify the system service when a
- * new session has been created.
+ * Callback interface definition for HDMI client to get informed of
+ * the result of various API invocation.
+ *
* @hide
*/
-oneway interface ITvInputSessionCallback {
- void onSessionCreated(ITvInputSession session);
+oneway interface IHdmiControlCallback {
+ void onComplete(int result);
}
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
new file mode 100644
index 0000000..8da38e1
--- /dev/null
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.hdmi.IHdmiHotplugEventListener;
+
+/**
+ * Binder interface that clients running in the application process
+ * will use to perform HDMI-CEC features by communicating with other devices
+ * on the bus.
+ *
+ * @hide
+ */
+interface IHdmiControlService {
+ int[] getSupportedTypes();
+ void oneTouchPlay(IHdmiControlCallback callback);
+ void queryDisplayStatus(IHdmiControlCallback callback);
+ void addHotplugEventListener(IHdmiHotplugEventListener listener);
+ void removeHotplugEventListener(IHdmiHotplugEventListener listener);
+}
diff --git a/core/java/android/tv/ITvInputServiceCallback.aidl b/core/java/android/hardware/hdmi/IHdmiHotplugEventListener.aidl
index e535c81..5d63264 100644
--- a/core/java/android/tv/ITvInputServiceCallback.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiHotplugEventListener.aidl
@@ -14,15 +14,16 @@
* limitations under the License.
*/
-package android.tv;
+package android.hardware.hdmi;
-import android.content.ComponentName;
+import android.hardware.hdmi.HdmiHotplugEvent;
/**
- * Helper interface for ITvInputService to allow the TV input to notify the client when its status
- * has been changed.
+ * Callback interface definition for HDMI client to get informed of
+ * the result of various API invocation.
+ *
* @hide
*/
-oneway interface ITvInputServiceCallback {
- void onAvailabilityChanged(in ComponentName name, boolean isAvailable);
+oneway interface IHdmiHotplugEventListener {
+ void onReceived(in HdmiHotplugEvent event);
}
diff --git a/core/java/android/hardware/usb/UsbConfiguration.java b/core/java/android/hardware/usb/UsbConfiguration.java
index 92d6f75..da5c128 100644
--- a/core/java/android/hardware/usb/UsbConfiguration.java
+++ b/core/java/android/hardware/usb/UsbConfiguration.java
@@ -44,13 +44,13 @@ public class UsbConfiguration implements Parcelable {
* Mask for "self-powered" bit in the configuration's attributes.
* @see #getAttributes
*/
- public static final int ATTR_SELF_POWERED_MASK = 1 << 6;
+ private static final int ATTR_SELF_POWERED = 1 << 6;
/**
* Mask for "remote wakeup" bit in the configuration's attributes.
* @see #getAttributes
*/
- public static final int ATTR_REMOTE_WAKEUP_MASK = 1 << 5;
+ private static final int ATTR_REMOTE_WAKEUP = 1 << 5;
/**
* UsbConfiguration should only be instantiated by UsbService implementation
@@ -83,19 +83,23 @@ public class UsbConfiguration implements Parcelable {
}
/**
- * Returns the configuration's attributes field.
- * This field contains a bit field with the following flags:
+ * Returns the self-powered attribute value configuration's attributes field.
+ * This attribute indicates that the device has a power source other than the USB connection.
*
- * 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
+ * @return the configuration's self-powered attribute
*/
- public int getAttributes() {
- return mAttributes;
+ public boolean isSelfPowered() {
+ return (mAttributes & ATTR_SELF_POWERED) != 0;
+ }
+
+ /**
+ * Returns the remote-wakeup attribute value configuration's attributes field.
+ * This attributes that the device may signal the host to wake from suspend.
+ *
+ * @return the configuration's remote-wakeup attribute
+ */
+ public boolean isRemoteWakeup() {
+ return (mAttributes & ATTR_REMOTE_WAKEUP) != 0;
}
/**
diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java
index 6283951..c062b3a 100644
--- a/core/java/android/hardware/usb/UsbDeviceConnection.java
+++ b/core/java/android/hardware/usb/UsbDeviceConnection.java
@@ -104,7 +104,7 @@ 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
+ * @return true if the interface was successfully selected
*/
public boolean setInterface(UsbInterface intf) {
return native_set_interface(intf.getId(), intf.getAlternateSetting());
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 06d8e4a..857e335 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -69,6 +69,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
final WeakReference<AbstractInputMethodService> mTarget;
+ final Context mContext;
final HandlerCaller mCaller;
final WeakReference<InputMethod> mInputMethod;
final int mTargetSdkVersion;
@@ -111,8 +112,8 @@ class IInputMethodWrapper extends IInputMethod.Stub
public IInputMethodWrapper(AbstractInputMethodService context,
InputMethod inputMethod) {
mTarget = new WeakReference<AbstractInputMethodService>(context);
- mCaller = new HandlerCaller(context.getApplicationContext(), null,
- this, true /*asyncHandler*/);
+ mContext = context.getApplicationContext();
+ mCaller = new HandlerCaller(mContext, null, this, true /*asyncHandler*/);
mInputMethod = new WeakReference<InputMethod>(inputMethod);
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
}
@@ -186,7 +187,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
case DO_CREATE_SESSION: {
SomeArgs args = (SomeArgs)msg.obj;
inputMethod.createSession(new InputMethodSessionCallbackWrapper(
- mCaller.mContext, (InputChannel)args.arg1,
+ mContext, (InputChannel)args.arg1,
(IInputSessionCallback)args.arg2));
args.recycle();
return;
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 4bccaf1..3417de1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -39,6 +39,7 @@ import android.text.method.MovementMethod;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
+import android.view.Gravity;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -679,7 +680,7 @@ public class InputMethodService extends AbstractInputMethodService {
mInflater = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState,
- false);
+ WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
if (mHardwareAccelerated) {
mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
}
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index a9bace1..795117e 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -37,6 +37,8 @@ public class SoftInputWindow extends Dialog {
final Callback mCallback;
final KeyEvent.Callback mKeyEventCallback;
final KeyEvent.DispatcherState mDispatcherState;
+ final int mWindowType;
+ final int mGravity;
final boolean mTakesFocus;
private final Rect mBounds = new Rect();
@@ -64,12 +66,14 @@ public class SoftInputWindow extends Dialog {
*/
public SoftInputWindow(Context context, String name, int theme, Callback callback,
KeyEvent.Callback keyEventCallback, KeyEvent.DispatcherState dispatcherState,
- boolean takesFocus) {
+ int windowType, int gravity, boolean takesFocus) {
super(context, theme);
mName = name;
mCallback = callback;
mKeyEventCallback = keyEventCallback;
mDispatcherState = dispatcherState;
+ mWindowType = windowType;
+ mGravity = gravity;
mTakesFocus = takesFocus;
initDockWindow();
}
@@ -97,47 +101,6 @@ public class SoftInputWindow extends Dialog {
}
/**
- * Get the size of the DockWindow.
- *
- * @return If the DockWindow sticks to the top or bottom of the screen, the
- * return value is the height of the DockWindow, and its width is
- * equal to the width of the screen; If the DockWindow sticks to the
- * left or right of the screen, the return value is the width of the
- * DockWindow, and its height is equal to the height of the screen.
- */
- public int getSize() {
- WindowManager.LayoutParams lp = getWindow().getAttributes();
-
- if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) {
- return lp.height;
- } else {
- return lp.width;
- }
- }
-
- /**
- * Set the size of the DockWindow.
- *
- * @param size If the DockWindow sticks to the top or bottom of the screen,
- * <var>size</var> is the height of the DockWindow, and its width is
- * equal to the width of the screen; If the DockWindow sticks to the
- * left or right of the screen, <var>size</var> is the width of the
- * DockWindow, and its height is equal to the height of the screen.
- */
- public void setSize(int size) {
- WindowManager.LayoutParams lp = getWindow().getAttributes();
-
- if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) {
- lp.width = -1;
- lp.height = size;
- } else {
- lp.width = size;
- lp.height = -1;
- }
- getWindow().setAttributes(lp);
- }
-
- /**
* Set which boundary of the screen the DockWindow sticks to.
*
* @param gravity The boundary of the screen to stick. See {#link
@@ -147,18 +110,22 @@ public class SoftInputWindow extends Dialog {
*/
public void setGravity(int gravity) {
WindowManager.LayoutParams lp = getWindow().getAttributes();
-
- boolean oldIsVertical = (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM);
-
lp.gravity = gravity;
+ updateWidthHeight(lp);
+ getWindow().setAttributes(lp);
+ }
- boolean newIsVertical = (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM);
+ public int getGravity() {
+ return getWindow().getAttributes().gravity;
+ }
- if (oldIsVertical != newIsVertical) {
- int tmp = lp.width;
- lp.width = lp.height;
- lp.height = tmp;
- getWindow().setAttributes(lp);
+ private void updateWidthHeight(WindowManager.LayoutParams lp) {
+ if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) {
+ lp.width = WindowManager.LayoutParams.MATCH_PARENT;
+ lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ } else {
+ lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ lp.height = WindowManager.LayoutParams.MATCH_PARENT;
}
}
@@ -201,14 +168,11 @@ public class SoftInputWindow extends Dialog {
private void initDockWindow() {
WindowManager.LayoutParams lp = getWindow().getAttributes();
- lp.type = WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+ lp.type = mWindowType;
lp.setTitle(mName);
- lp.gravity = Gravity.BOTTOM;
- lp.width = -1;
- // Let the input method window's orientation follow sensor based rotation
- // Turn this off for now, it is very problematic.
- //lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
+ lp.gravity = mGravity;
+ updateWidthHeight(lp);
getWindow().setAttributes(lp);
diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java
index 804f8ee..79db389 100644
--- a/core/java/android/net/BaseNetworkStateTracker.java
+++ b/core/java/android/net/BaseNetworkStateTracker.java
@@ -44,7 +44,8 @@ public abstract class BaseNetworkStateTracker implements NetworkStateTracker {
protected NetworkInfo mNetworkInfo;
protected LinkProperties mLinkProperties;
- protected LinkCapabilities mLinkCapabilities;
+ protected NetworkCapabilities mNetworkCapabilities;
+ protected Network mNetwork = new Network(ConnectivityManager.INVALID_NET_ID);
private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
@@ -54,7 +55,7 @@ public abstract class BaseNetworkStateTracker implements NetworkStateTracker {
mNetworkInfo = new NetworkInfo(
networkType, -1, ConnectivityManager.getNetworkTypeName(networkType), null);
mLinkProperties = new LinkProperties();
- mLinkCapabilities = new LinkCapabilities();
+ mNetworkCapabilities = new NetworkCapabilities();
}
protected BaseNetworkStateTracker() {
@@ -98,8 +99,8 @@ public abstract class BaseNetworkStateTracker implements NetworkStateTracker {
}
@Override
- public LinkCapabilities getLinkCapabilities() {
- return new LinkCapabilities(mLinkCapabilities);
+ public NetworkCapabilities getNetworkCapabilities() {
+ return new NetworkCapabilities(mNetworkCapabilities);
}
@Override
@@ -201,4 +202,14 @@ public abstract class BaseNetworkStateTracker implements NetworkStateTracker {
public void stopSampling(SamplingDataTracker.SamplingSnapshot s) {
// nothing to do
}
+
+ @Override
+ public void setNetId(int netId) {
+ mNetwork = new Network(netId);
+ }
+
+ @Override
+ public Network getNetwork() {
+ return mNetwork;
+ }
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 30d7043..a48a388 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -13,26 +13,39 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.net;
import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.INetworkActivityListener;
import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
+import android.telephony.TelephonyManager;
import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.util.Protocol;
import java.net.InetAddress;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.HashMap;
/**
* Class that answers queries about the state of network connectivity. It also
@@ -48,13 +61,15 @@ import java.net.InetAddress;
* is lost</li>
* <li>Provide an API that allows applications to query the coarse-grained or fine-grained
* state of the available networks</li>
+ * <li>Provide an API that allows applications to request and select networks for their data
+ * traffic</li>
* </ol>
*/
public class ConnectivityManager {
private static final String TAG = "ConnectivityManager";
/**
- * A change in network connectivity has occurred. A connection has either
+ * A change in network connectivity has occurred. A default connection has either
* been established or lost. The NetworkInfo for the affected network is
* sent as an extra; it should be consulted to see what kind of
* connectivity event occurred.
@@ -408,6 +423,11 @@ public class ConnectivityManager {
*/
public static final int CONNECTIVITY_CHANGE_DELAY_DEFAULT = 3000;
+ /**
+ * @hide
+ */
+ public final static int INVALID_NET_ID = 0;
+
private final IConnectivityManager mService;
private final String mPackageName;
@@ -529,38 +549,32 @@ public class ConnectivityManager {
/**
* Specifies the preferred network type. When the device has more
* than one type available the preferred network type will be used.
- * Note that this made sense when we only had 2 network types,
- * but with more and more default networks we need an array to list
- * their ordering. This will be deprecated soon.
*
* @param preference the network type to prefer over all others. It is
* unspecified what happens to the old preferred network in the
* overall ordering.
+ * @deprecated Functionality has been removed as it no longer makes sense,
+ * with many more than two networks - we'd need an array to express
+ * preference. Instead we use dynamic network properties of
+ * the networks to describe their precedence.
*/
public void setNetworkPreference(int preference) {
- try {
- mService.setNetworkPreference(preference);
- } catch (RemoteException e) {
- }
}
/**
* Retrieves the current preferred network type.
- * Note that this made sense when we only had 2 network types,
- * but with more and more default networks we need an array to list
- * their ordering. This will be deprecated soon.
*
* @return an integer representing the preferred network type
*
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * @deprecated Functionality has been removed as it no longer makes sense,
+ * with many more than two networks - we'd need an array to express
+ * preference. Instead we use dynamic network properties of
+ * the networks to describe their precedence.
*/
public int getNetworkPreference() {
- try {
- return mService.getNetworkPreference();
- } catch (RemoteException e) {
- return -1;
- }
+ return TYPE_NONE;
}
/**
@@ -700,7 +714,37 @@ public class ConnectivityManager {
*/
public LinkProperties getLinkProperties(int networkType) {
try {
- return mService.getLinkProperties(networkType);
+ return mService.getLinkPropertiesForType(networkType);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Get the {@link LinkProperties} for the given {@link Network}. This
+ * will return {@code null} if the network is unknown.
+ *
+ * @param network The {@link Network} object identifying the network in question.
+ * @return The {@link LinkProperties} for the network, or {@code null}.
+ **/
+ public LinkProperties getLinkProperties(Network network) {
+ try {
+ return mService.getLinkProperties(network);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Get the {@link NetworkCapabilities} for the given {@link Network}. This
+ * will return {@code null} if the network is unknown.
+ *
+ * @param network The {@link Network} object identifying the network in question.
+ * @return The {@link NetworkCapabilities} for the network, or {@code null}.
+ */
+ public NetworkCapabilities getNetworkCapabilities(Network network) {
+ try {
+ return mService.getNetworkCapabilities(network);
} catch (RemoteException e) {
return null;
}
@@ -718,13 +762,14 @@ public class ConnectivityManager {
* {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
* {@hide}
*/
- public boolean setRadios(boolean turnOn) {
- try {
- return mService.setRadios(turnOn);
- } catch (RemoteException e) {
- return false;
- }
- }
+// TODO - check for any callers and remove
+// public boolean setRadios(boolean turnOn) {
+// try {
+// return mService.setRadios(turnOn);
+// } catch (RemoteException e) {
+// return false;
+// }
+// }
/**
* Tells a given networkType to set its radio power state as directed.
@@ -738,13 +783,14 @@ public class ConnectivityManager {
* {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
* {@hide}
*/
- public boolean setRadio(int networkType, boolean turnOn) {
- try {
- return mService.setRadio(networkType, turnOn);
- } catch (RemoteException e) {
- return false;
- }
- }
+// TODO - check for any callers and remove
+// public boolean setRadio(int networkType, boolean turnOn) {
+// try {
+// return mService.setRadio(networkType, turnOn);
+// } catch (RemoteException e) {
+// return false;
+// }
+// }
/**
* Tells the underlying networking system that the caller wants to
@@ -758,13 +804,38 @@ public class ConnectivityManager {
* The interpretation of this value is specific to each networking
* implementation+feature combination, except that the value {@code -1}
* always indicates failure.
+ *
+ * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api.
*/
public int startUsingNetworkFeature(int networkType, String feature) {
- try {
- return mService.startUsingNetworkFeature(networkType, feature,
- new Binder());
- } catch (RemoteException e) {
- return -1;
+ NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
+ if (netCap == null) {
+ Log.d(TAG, "Can't satisfy startUsingNetworkFeature for " + networkType + ", " +
+ feature);
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+
+ NetworkRequest request = null;
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.get(netCap);
+ if (l != null) {
+ Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest);
+ renewRequestLocked(l);
+ if (l.currentNetwork != null) {
+ return PhoneConstants.APN_ALREADY_ACTIVE;
+ } else {
+ return PhoneConstants.APN_REQUEST_STARTED;
+ }
+ }
+
+ request = requestNetworkForFeatureLocked(netCap);
+ }
+ if (request != null) {
+ Log.d(TAG, "starting startUsingNeworkFeature for request " + request);
+ return PhoneConstants.APN_REQUEST_STARTED;
+ } else {
+ Log.d(TAG, " request Failed");
+ return PhoneConstants.APN_REQUEST_FAILED;
}
}
@@ -780,13 +851,176 @@ public class ConnectivityManager {
* The interpretation of this value is specific to each networking
* implementation+feature combination, except that the value {@code -1}
* always indicates failure.
+ *
+ * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api.
*/
public int stopUsingNetworkFeature(int networkType, String feature) {
- try {
- return mService.stopUsingNetworkFeature(networkType, feature);
- } catch (RemoteException e) {
+ NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
+ if (netCap == null) {
+ Log.d(TAG, "Can't satisfy stopUsingNetworkFeature for " + networkType + ", " +
+ feature);
return -1;
}
+
+ NetworkRequest request = removeRequestForFeature(netCap);
+ if (request != null) {
+ Log.d(TAG, "stopUsingNetworkFeature for " + networkType + ", " + feature);
+ releaseNetworkRequest(request);
+ }
+ return 1;
+ }
+
+ private NetworkCapabilities networkCapabilitiesForFeature(int networkType, String feature) {
+ if (networkType == TYPE_MOBILE) {
+ int cap = -1;
+ if ("enableMMS".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_MMS;
+ } else if ("enableSUPL".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_SUPL;
+ } else if ("enableDUN".equals(feature) || "enableDUNAlways".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_DUN;
+ } else if ("enableHIPRI".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_INTERNET;
+ } else if ("enableFOTA".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_FOTA;
+ } else if ("enableIMS".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_IMS;
+ } else if ("enableCBS".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_CBS;
+ } else {
+ return null;
+ }
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+ netCap.addNetworkCapability(cap);
+ return netCap;
+ } else if (networkType == TYPE_WIFI) {
+ if ("p2p".equals(feature)) {
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+ netCap.addNetworkCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P);
+ return netCap;
+ }
+ }
+ return null;
+ }
+
+ private int legacyTypeForNetworkCapabilities(NetworkCapabilities netCap) {
+ if (netCap == null) return TYPE_NONE;
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
+ return TYPE_MOBILE_CBS;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+ return TYPE_MOBILE_IMS;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
+ return TYPE_MOBILE_FOTA;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
+ return TYPE_MOBILE_DUN;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
+ return TYPE_MOBILE_SUPL;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+ return TYPE_MOBILE_MMS;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+ return TYPE_MOBILE_HIPRI;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P)) {
+ return TYPE_WIFI_P2P;
+ }
+ return TYPE_NONE;
+ }
+
+ private static class LegacyRequest {
+ NetworkCapabilities networkCapabilities;
+ NetworkRequest networkRequest;
+ int expireSequenceNumber;
+ Network currentNetwork;
+ int delay = -1;
+ NetworkCallbackListener networkCallbackListener = new NetworkCallbackListener() {
+ @Override
+ public void onAvailable(NetworkRequest request, Network network) {
+ currentNetwork = network;
+ Log.d(TAG, "startUsingNetworkFeature got Network:" + network);
+ network.bindProcessForHostResolution();
+ }
+ @Override
+ public void onLost(NetworkRequest request, Network network) {
+ if (network.equals(currentNetwork)) {
+ currentNetwork = null;
+ network.unbindProcessForHostResolution();
+ }
+ Log.d(TAG, "startUsingNetworkFeature lost Network:" + network);
+ }
+ };
+ }
+
+ private HashMap<NetworkCapabilities, LegacyRequest> sLegacyRequests =
+ new HashMap<NetworkCapabilities, LegacyRequest>();
+
+ private NetworkRequest findRequestForFeature(NetworkCapabilities netCap) {
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.get(netCap);
+ if (l != null) return l.networkRequest;
+ }
+ return null;
+ }
+
+ private void renewRequestLocked(LegacyRequest l) {
+ l.expireSequenceNumber++;
+ Log.d(TAG, "renewing request to seqNum " + l.expireSequenceNumber);
+ sendExpireMsgForFeature(l.networkCapabilities, l.expireSequenceNumber, l.delay);
+ }
+
+ private void expireRequest(NetworkCapabilities netCap, int sequenceNum) {
+ int ourSeqNum = -1;
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.get(netCap);
+ if (l == null) return;
+ ourSeqNum = l.expireSequenceNumber;
+ if (l.expireSequenceNumber == sequenceNum) {
+ releaseNetworkRequest(l.networkRequest);
+ sLegacyRequests.remove(netCap);
+ }
+ }
+ Log.d(TAG, "expireRequest with " + ourSeqNum + ", " + sequenceNum);
+ }
+
+ private NetworkRequest requestNetworkForFeatureLocked(NetworkCapabilities netCap) {
+ int delay = -1;
+ int type = legacyTypeForNetworkCapabilities(netCap);
+ try {
+ delay = mService.getRestoreDefaultNetworkDelay(type);
+ } catch (RemoteException e) {}
+ LegacyRequest l = new LegacyRequest();
+ l.networkCapabilities = netCap;
+ l.delay = delay;
+ l.expireSequenceNumber = 0;
+ l.networkRequest = sendRequestForNetwork(netCap, l.networkCallbackListener, 0,
+ REQUEST, type);
+ if (l.networkRequest == null) return null;
+ sLegacyRequests.put(netCap, l);
+ sendExpireMsgForFeature(netCap, l.expireSequenceNumber, delay);
+ return l.networkRequest;
+ }
+
+ private void sendExpireMsgForFeature(NetworkCapabilities netCap, int seqNum, int delay) {
+ if (delay >= 0) {
+ Log.d(TAG, "sending expire msg with seqNum " + seqNum + " and delay " + delay);
+ Message msg = sCallbackHandler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap);
+ sCallbackHandler.sendMessageDelayed(msg, delay);
+ }
+ }
+
+ private NetworkRequest removeRequestForFeature(NetworkCapabilities netCap) {
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.remove(netCap);
+ if (l == null) return null;
+ return l.networkRequest;
+ }
}
/**
@@ -799,6 +1033,9 @@ public class ConnectivityManager {
* host is to be routed
* @param hostAddress the IP address of the host to which the route is desired
* @return {@code true} on success, {@code false} on failure
+ *
+ * @deprecated Deprecated in favor of the {@link #requestNetwork},
+ * {@link Network#bindProcess} and {@link Network#socketFactory} api.
*/
public boolean requestRouteToHost(int networkType, int hostAddress) {
InetAddress inetAddress = NetworkUtils.intToInetAddress(hostAddress);
@@ -821,6 +1058,8 @@ public class ConnectivityManager {
* @param hostAddress the IP address of the host to which the route is desired
* @return {@code true} on success, {@code false} on failure
* @hide
+ * @deprecated Deprecated in favor of the {@link #requestNetwork} and
+ * {@link Network#bindProcess} api.
*/
public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) {
byte[] address = hostAddress.getAddress();
@@ -888,34 +1127,18 @@ public class ConnectivityManager {
}
/**
- * Gets the value of the setting for enabling Mobile data.
- *
- * @return Whether mobile data is enabled.
- *
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* @hide
+ * @deprecated Talk to TelephonyManager directly
*/
public boolean getMobileDataEnabled() {
- try {
- return mService.getMobileDataEnabled();
- } catch (RemoteException e) {
- return true;
- }
- }
-
- /**
- * Sets the persisted value for enabling/disabling Mobile data.
- *
- * @param enabled Whether the user wants the mobile data connection used
- * or not.
- * @hide
- */
- public void setMobileDataEnabled(boolean enabled) {
- try {
- mService.setMobileDataEnabled(enabled);
- } catch (RemoteException e) {
+ IBinder b = ServiceManager.getService(Context.TELEPHONY_SERVICE);
+ if (b != null) {
+ try {
+ ITelephony it = ITelephony.Stub.asInterface(b);
+ return it.getDataEnabled();
+ } catch (RemoteException e) { }
}
+ return false;
}
/**
@@ -1302,6 +1525,22 @@ public class ConnectivityManager {
}
/**
+ * Report a problem network to the framework. This provides a hint to the system
+ * that there might be connectivity problems on this network and may cause
+ * the framework to re-evaluate network connectivity and/or switch to another
+ * network.
+ *
+ * @param network The {@link Network} the application was attempting to use
+ * or {@code null} to indicate the current default network.
+ */
+ public void reportBadNetwork(Network network) {
+ try {
+ mService.reportBadNetwork(network);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Set a network-independent global http proxy. This is not normally what you want
* for typical HTTP proxies - they are general network dependent. However if you're
* doing something unusual like general internal filtering this may be useful. On
@@ -1312,6 +1551,7 @@ public class ConnectivityManager {
*
* <p>This method requires the call to hold the permission
* android.Manifest.permission#CONNECTIVITY_INTERNAL.
+ * @hide
*/
public void setGlobalProxy(ProxyInfo p) {
try {
@@ -1328,6 +1568,7 @@ public class ConnectivityManager {
*
* <p>This method requires the call to hold the permission
* {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * @hide
*/
public ProxyInfo getGlobalProxy() {
try {
@@ -1347,6 +1588,7 @@ public class ConnectivityManager {
* <p>This method requires the call to hold the permission
* {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
+ * @deprecated Deprecated in favor of {@link #getLinkProperties}
*/
public ProxyInfo getProxy() {
try {
@@ -1582,4 +1824,491 @@ public class ConnectivityManager {
} catch (RemoteException e) {
}
}
+
+ /** {@hide} */
+ public void registerNetworkFactory(Messenger messenger, String name) {
+ try {
+ mService.registerNetworkFactory(messenger, name);
+ } catch (RemoteException e) { }
+ }
+
+ /** {@hide} */
+ public void unregisterNetworkFactory(Messenger messenger) {
+ try {
+ mService.unregisterNetworkFactory(messenger);
+ } catch (RemoteException e) { }
+ }
+
+ /** {@hide} */
+ public void registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
+ NetworkCapabilities nc, int score) {
+ try {
+ mService.registerNetworkAgent(messenger, ni, lp, nc, score);
+ } catch (RemoteException e) { }
+ }
+
+ /**
+ * Base class for NetworkRequest callbacks. Used for notifications about network
+ * changes. Should be extended by applications wanting notifications.
+ */
+ public static class NetworkCallbackListener {
+ /** @hide */
+ public static final int PRECHECK = 1;
+ /** @hide */
+ public static final int AVAILABLE = 2;
+ /** @hide */
+ public static final int LOSING = 3;
+ /** @hide */
+ public static final int LOST = 4;
+ /** @hide */
+ public static final int UNAVAIL = 5;
+ /** @hide */
+ public static final int CAP_CHANGED = 6;
+ /** @hide */
+ public static final int PROP_CHANGED = 7;
+ /** @hide */
+ public static final int CANCELED = 8;
+
+ /**
+ * @hide
+ * Called whenever the framework connects to a network that it may use to
+ * satisfy this request
+ */
+ public void onPreCheck(NetworkRequest networkRequest, Network network) {}
+
+ /**
+ * Called when the framework connects and has declared new network ready for use.
+ *
+ * @param networkRequest The {@link NetworkRequest} used to initiate the request.
+ * @param network The {@link Network} of the satisfying network.
+ */
+ public void onAvailable(NetworkRequest networkRequest, Network network) {}
+
+ /**
+ * Called when the network is about to be disconnected. Often paired with an
+ * {@link NetworkCallbackListener#onAvailable} call with the new replacement network
+ * for graceful handover. This may not be called if we have a hard loss
+ * (loss without warning). This may be followed by either a
+ * {@link NetworkCallbackListener#onLost} call or a
+ * {@link NetworkCallbackListener#onAvailable} call for this network depending
+ * on whether we lose or regain it.
+ *
+ * @param networkRequest The {@link NetworkRequest} used to initiate the request.
+ * @param network The {@link Network} of the failing network.
+ * @param maxSecToLive The time in seconds the framework will attempt to keep the
+ * network connected. Note that the network may suffers a
+ * hard loss at any time.
+ */
+ public void onLosing(NetworkRequest networkRequest, Network network, int maxSecToLive) {}
+
+ /**
+ * Called when the framework has a hard loss of the network or when the
+ * graceful failure ends.
+ *
+ * @param networkRequest The {@link NetworkRequest} used to initiate the request.
+ * @param network The {@link Network} lost.
+ */
+ public void onLost(NetworkRequest networkRequest, Network network) {}
+
+ /**
+ * Called if no network is found in the given timeout time. If no timeout is given,
+ * this will not be called.
+ * @hide
+ */
+ public void onUnavailable(NetworkRequest networkRequest) {}
+
+ /**
+ * Called when the network the framework connected to for this request
+ * changes capabilities but still satisfies the stated need.
+ *
+ * @param networkRequest The {@link NetworkRequest} used to initiate the request.
+ * @param network The {@link Network} whose capabilities have changed.
+ * @param networkCapabilities The new {@link NetworkCapabilities} for this network.
+ */
+ public void onNetworkCapabilitiesChanged(NetworkRequest networkRequest, Network network,
+ NetworkCapabilities networkCapabilities) {}
+
+ /**
+ * Called when the network the framework connected to for this request
+ * changes {@link LinkProperties}.
+ *
+ * @param networkRequest The {@link NetworkRequest} used to initiate the request.
+ * @param network The {@link Network} whose link properties have changed.
+ * @param linkProperties The new {@link LinkProperties} for this network.
+ */
+ public void onLinkPropertiesChanged(NetworkRequest networkRequest, Network network,
+ LinkProperties linkProperties) {}
+
+ /**
+ * Called when a {@link #releaseNetworkRequest} call concludes and the registered
+ * callbacks will no longer be used.
+ *
+ * @param networkRequest The {@link NetworkRequest} used to initiate the request.
+ */
+ public void onReleased(NetworkRequest networkRequest) {}
+ }
+
+ private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER;
+ /** @hide obj = pair(NetworkRequest, Network) */
+ public static final int CALLBACK_PRECHECK = BASE + 1;
+ /** @hide obj = pair(NetworkRequest, Network) */
+ public static final int CALLBACK_AVAILABLE = BASE + 2;
+ /** @hide obj = pair(NetworkRequest, Network), arg1 = ttl */
+ public static final int CALLBACK_LOSING = BASE + 3;
+ /** @hide obj = pair(NetworkRequest, Network) */
+ public static final int CALLBACK_LOST = BASE + 4;
+ /** @hide obj = NetworkRequest */
+ public static final int CALLBACK_UNAVAIL = BASE + 5;
+ /** @hide obj = pair(NetworkRequest, Network) */
+ public static final int CALLBACK_CAP_CHANGED = BASE + 6;
+ /** @hide obj = pair(NetworkRequest, Network) */
+ public static final int CALLBACK_IP_CHANGED = BASE + 7;
+ /** @hide obj = NetworkRequest */
+ public static final int CALLBACK_RELEASED = BASE + 8;
+ /** @hide */
+ public static final int CALLBACK_EXIT = BASE + 9;
+ /** @hide obj = NetworkCapabilities, arg1 = seq number */
+ private static final int EXPIRE_LEGACY_REQUEST = BASE + 10;
+
+ private class CallbackHandler extends Handler {
+ private final HashMap<NetworkRequest, NetworkCallbackListener>mCallbackMap;
+ private final AtomicInteger mRefCount;
+ private static final String TAG = "ConnectivityManager.CallbackHandler";
+ private final ConnectivityManager mCm;
+
+ CallbackHandler(Looper looper, HashMap<NetworkRequest, NetworkCallbackListener>callbackMap,
+ AtomicInteger refCount, ConnectivityManager cm) {
+ super(looper);
+ mCallbackMap = callbackMap;
+ mRefCount = refCount;
+ mCm = cm;
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ Log.d(TAG, "CM callback handler got msg " + message.what);
+ switch (message.what) {
+ case CALLBACK_PRECHECK: {
+ NetworkRequest request = getNetworkRequest(message);
+ NetworkCallbackListener callbacks = getCallbacks(request);
+ if (callbacks != null) {
+ callbacks.onPreCheck(request, getNetwork(message));
+ } else {
+ Log.e(TAG, "callback not found for PRECHECK message");
+ }
+ break;
+ }
+ case CALLBACK_AVAILABLE: {
+ NetworkRequest request = getNetworkRequest(message);
+ NetworkCallbackListener callbacks = getCallbacks(request);
+ if (callbacks != null) {
+ callbacks.onAvailable(request, getNetwork(message));
+ } else {
+ Log.e(TAG, "callback not found for AVAILABLE message");
+ }
+ break;
+ }
+ case CALLBACK_LOSING: {
+ NetworkRequest request = getNetworkRequest(message);
+ NetworkCallbackListener callbacks = getCallbacks(request);
+ if (callbacks != null) {
+ callbacks.onLosing(request, getNetwork(message), message.arg1);
+ } else {
+ Log.e(TAG, "callback not found for LOSING message");
+ }
+ break;
+ }
+ case CALLBACK_LOST: {
+ NetworkRequest request = getNetworkRequest(message);
+ NetworkCallbackListener callbacks = getCallbacks(request);
+ if (callbacks != null) {
+ callbacks.onLost(request, getNetwork(message));
+ } else {
+ Log.e(TAG, "callback not found for LOST message");
+ }
+ break;
+ }
+ case CALLBACK_UNAVAIL: {
+ NetworkRequest req = (NetworkRequest)message.obj;
+ NetworkCallbackListener callbacks = null;
+ synchronized(mCallbackMap) {
+ callbacks = mCallbackMap.get(req);
+ }
+ if (callbacks != null) {
+ callbacks.onUnavailable(req);
+ } else {
+ Log.e(TAG, "callback not found for UNAVAIL message");
+ }
+ break;
+ }
+ case CALLBACK_CAP_CHANGED: {
+ NetworkRequest request = getNetworkRequest(message);
+ NetworkCallbackListener callbacks = getCallbacks(request);
+ if (callbacks != null) {
+ Network network = getNetwork(message);
+ NetworkCapabilities cap = mCm.getNetworkCapabilities(network);
+
+ callbacks.onNetworkCapabilitiesChanged(request, network, cap);
+ } else {
+ Log.e(TAG, "callback not found for CHANGED message");
+ }
+ break;
+ }
+ case CALLBACK_IP_CHANGED: {
+ NetworkRequest request = getNetworkRequest(message);
+ NetworkCallbackListener callbacks = getCallbacks(request);
+ if (callbacks != null) {
+ Network network = getNetwork(message);
+ LinkProperties lp = mCm.getLinkProperties(network);
+
+ callbacks.onLinkPropertiesChanged(request, network, lp);
+ } else {
+ Log.e(TAG, "callback not found for CHANGED message");
+ }
+ break;
+ }
+ case CALLBACK_RELEASED: {
+ NetworkRequest req = (NetworkRequest)message.obj;
+ NetworkCallbackListener callbacks = null;
+ synchronized(mCallbackMap) {
+ callbacks = mCallbackMap.remove(req);
+ }
+ if (callbacks != null) {
+ callbacks.onReleased(req);
+ } else {
+ Log.e(TAG, "callback not found for CANCELED message");
+ }
+ synchronized(mRefCount) {
+ if (mRefCount.decrementAndGet() == 0) {
+ getLooper().quit();
+ }
+ }
+ break;
+ }
+ case CALLBACK_EXIT: {
+ Log.d(TAG, "Listener quiting");
+ getLooper().quit();
+ break;
+ }
+ case EXPIRE_LEGACY_REQUEST: {
+ expireRequest((NetworkCapabilities)message.obj, message.arg1);
+ break;
+ }
+ }
+ }
+
+ private NetworkRequest getNetworkRequest(Message msg) {
+ return (NetworkRequest)(msg.obj);
+ }
+ private NetworkCallbackListener getCallbacks(NetworkRequest req) {
+ synchronized(mCallbackMap) {
+ return mCallbackMap.get(req);
+ }
+ }
+ private Network getNetwork(Message msg) {
+ return new Network(msg.arg2);
+ }
+ private NetworkCallbackListener removeCallbacks(Message msg) {
+ NetworkRequest req = (NetworkRequest)msg.obj;
+ synchronized(mCallbackMap) {
+ return mCallbackMap.remove(req);
+ }
+ }
+ }
+
+ private void addCallbackListener() {
+ synchronized(sCallbackRefCount) {
+ if (sCallbackRefCount.incrementAndGet() == 1) {
+ // TODO - switch this over to a ManagerThread or expire it when done
+ HandlerThread callbackThread = new HandlerThread("ConnectivityManager");
+ callbackThread.start();
+ sCallbackHandler = new CallbackHandler(callbackThread.getLooper(),
+ sNetworkCallbackListener, sCallbackRefCount, this);
+ }
+ }
+ }
+
+ private void removeCallbackListener() {
+ synchronized(sCallbackRefCount) {
+ if (sCallbackRefCount.decrementAndGet() == 0) {
+ sCallbackHandler.obtainMessage(CALLBACK_EXIT).sendToTarget();
+ sCallbackHandler = null;
+ }
+ }
+ }
+
+ static final HashMap<NetworkRequest, NetworkCallbackListener> sNetworkCallbackListener =
+ new HashMap<NetworkRequest, NetworkCallbackListener>();
+ static final AtomicInteger sCallbackRefCount = new AtomicInteger(0);
+ static CallbackHandler sCallbackHandler = null;
+
+ private final static int LISTEN = 1;
+ private final static int REQUEST = 2;
+
+ private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
+ NetworkCallbackListener networkCallbackListener, int timeoutSec, int action,
+ int legacyType) {
+ NetworkRequest networkRequest = null;
+ if (networkCallbackListener == null) {
+ throw new IllegalArgumentException("null NetworkCallbackListener");
+ }
+ if (need == null) throw new IllegalArgumentException("null NetworkCapabilities");
+ try {
+ addCallbackListener();
+ if (action == LISTEN) {
+ networkRequest = mService.listenForNetwork(need, new Messenger(sCallbackHandler),
+ new Binder());
+ } else {
+ networkRequest = mService.requestNetwork(need, new Messenger(sCallbackHandler),
+ timeoutSec, new Binder(), legacyType);
+ }
+ if (networkRequest != null) {
+ synchronized(sNetworkCallbackListener) {
+ sNetworkCallbackListener.put(networkRequest, networkCallbackListener);
+ }
+ }
+ } catch (RemoteException e) {}
+ if (networkRequest == null) removeCallbackListener();
+ return networkRequest;
+ }
+
+ /**
+ * Request a network to satisfy a set of {@link NetworkCapabilities}.
+ *
+ * This {@link NetworkRequest} will live until released via
+ * {@link #releaseNetworkRequest} or the calling application exits.
+ * Status of the request can be followed by listening to the various
+ * callbacks described in {@link NetworkCallbackListener}. The {@link Network}
+ * can be used to direct traffic to the network.
+ *
+ * @param need {@link NetworkCapabilities} required by this request.
+ * @param networkCallbackListener The {@link NetworkCallbackListener} to be utilized for this
+ * request. Note the callbacks can be shared by multiple
+ * requests and the NetworkRequest token utilized to
+ * determine to which request the callback relates.
+ * @return A {@link NetworkRequest} object identifying the request.
+ */
+ public NetworkRequest requestNetwork(NetworkCapabilities need,
+ NetworkCallbackListener networkCallbackListener) {
+ return sendRequestForNetwork(need, networkCallbackListener, 0, REQUEST, TYPE_NONE);
+ }
+
+ /**
+ * Request a network to satisfy a set of {@link NetworkCapabilities}, limited
+ * by a timeout.
+ *
+ * This function behaves identically to the non-timedout version, but if a suitable
+ * network is not found within the given time (in Seconds) the
+ * {@link NetworkCallbackListener#unavailable} callback is called. The request must
+ * still be released normally by calling {@link releaseNetworkRequest}.
+ * @param need {@link NetworkCapabilities} required by this request.
+ * @param networkCallbackListener The callbacks to be utilized for this request. Note
+ * the callbacks can be shared by multiple requests and
+ * the NetworkRequest token utilized to determine to which
+ * request the callback relates.
+ * @param timeoutSec The time in seconds to attempt looking for a suitable network
+ * before {@link NetworkCallbackListener#unavailable} is called.
+ * @return A {@link NetworkRequest} object identifying the request.
+ * @hide
+ */
+ public NetworkRequest requestNetwork(NetworkCapabilities need,
+ NetworkCallbackListener networkCallbackListener, int timeoutSec) {
+ return sendRequestForNetwork(need, networkCallbackListener, timeoutSec, REQUEST,
+ TYPE_NONE);
+ }
+
+ /**
+ * The maximum number of seconds the framework will look for a suitable network
+ * during a timeout-equiped call to {@link requestNetwork}.
+ * {@hide}
+ */
+ public final static int MAX_NETWORK_REQUEST_TIMEOUT_SEC = 100 * 60;
+
+ /**
+ * The lookup key for a {@link Network} object included with the intent after
+ * succesfully finding a network for the applications request. Retrieve it with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ */
+ public static final String EXTRA_NETWORK_REQUEST_NETWORK = "networkRequestNetwork";
+
+ /**
+ * The lookup key for a {@link NetworkCapabilities} object included with the intent after
+ * succesfully finding a network for the applications request. Retrieve it with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ */
+ public static final String EXTRA_NETWORK_REQUEST_NETWORK_CAPABILITIES =
+ "networkRequestNetworkCapabilities";
+
+
+ /**
+ * Request a network to satisfy a set of {@link NetworkCapabilities}.
+ *
+ * This function behavies identically to the callback-equiped version, but instead
+ * of {@link NetworkCallbackListener} a {@link PendingIntent} is used. This means
+ * the request may outlive the calling application and get called back when a suitable
+ * network is found.
+ * <p>
+ * The operation is an Intent broadcast that goes to a broadcast receiver that
+ * you registered with {@link Context#registerReceiver} or through the
+ * &lt;receiver&gt; tag in an AndroidManifest.xml file
+ * <p>
+ * The operation Intent is delivered with two extras, a {@link Network} typed
+ * extra called {@link #EXTRA_NETWORK_REQUEST_NETWORK} and a {@link NetworkCapabilities}
+ * typed extra called {@link #EXTRA_NETWORK_REQUEST_NETWORK_CAPABILITIES} containing
+ * the original requests parameters. It is important to create a new,
+ * {@link NetworkCallbackListener} based request before completing the processing of the
+ * Intent to reserve the network or it will be released shortly after the Intent
+ * is processed.
+ * <p>
+ * If there is already an request for this Intent registered (with the equality of
+ * two Intents defined by {@link Intent#filterEquals}), then it will be removed and
+ * replaced by this one, effectively releasing the previous {@link NetworkRequest}.
+ * <p>
+ * The request may be released normally by calling {@link #releaseNetworkRequest}.
+ *
+ * @param need {@link NetworkCapabilities} required by this request.
+ * @param operation Action to perform when the network is available (corresponds
+ * to the {@link NetworkCallbackListener#onAvailable} call. Typically
+ * comes from {@link PendingIntent#getBroadcast}.
+ * @return A {@link NetworkRequest} object identifying the request.
+ */
+ public NetworkRequest requestNetwork(NetworkCapabilities need, PendingIntent operation) {
+ try {
+ return mService.pendingRequestForNetwork(need, operation);
+ } catch (RemoteException e) {}
+ return null;
+ }
+
+ /**
+ * Registers to receive notifications about all networks which satisfy the given
+ * {@link NetworkCapabilities}. The callbacks will continue to be called until
+ * either the application exits or the request is released using
+ * {@link #releaseNetworkRequest}.
+ *
+ * @param need {@link NetworkCapabilities} required by this request.
+ * @param networkCallbackListener The {@link NetworkCallbackListener} to be called as suitable
+ * networks change state.
+ * @return A {@link NetworkRequest} object identifying the request.
+ */
+ public NetworkRequest listenForNetwork(NetworkCapabilities need,
+ NetworkCallbackListener networkCallbackListener) {
+ return sendRequestForNetwork(need, networkCallbackListener, 0, LISTEN, TYPE_NONE);
+ }
+
+ /**
+ * Releases a {@link NetworkRequest} generated either through a {@link #requestNetwork}
+ * or a {@link #listenForNetwork} call. The {@link NetworkCallbackListener} given in the
+ * earlier call may continue receiving calls until the
+ * {@link NetworkCallbackListener#onReleased} function is called, signifying the end
+ * of the request.
+ *
+ * @param networkRequest The {@link NetworkRequest} generated by an earlier call to
+ * {@link #requestNetwork} or {@link #listenForNetwork}.
+ */
+ public void releaseNetworkRequest(NetworkRequest networkRequest) {
+ if (networkRequest == null) throw new IllegalArgumentException("null NetworkRequest");
+ try {
+ mService.releaseNetworkRequest(networkRequest);
+ } catch (RemoteException e) {}
+ }
}
diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java
index a5d059e..eff9f9f 100644
--- a/core/java/android/net/DummyDataStateTracker.java
+++ b/core/java/android/net/DummyDataStateTracker.java
@@ -190,13 +190,6 @@ public class DummyDataStateTracker extends BaseNetworkStateTracker {
return new LinkProperties(mLinkProperties);
}
- /**
- * @see android.net.NetworkStateTracker#getLinkCapabilities()
- */
- public LinkCapabilities getLinkCapabilities() {
- return new LinkCapabilities(mLinkCapabilities);
- }
-
public void setDependencyMet(boolean met) {
// not supported on this network
}
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
deleted file mode 100644
index 10b5d0b..0000000
--- a/core/java/android/net/EthernetDataTracker.java
+++ /dev/null
@@ -1,437 +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.net;
-
-import android.content.Context;
-import android.net.NetworkInfo.DetailedState;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.INetworkManagementService;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
-
-import com.android.server.net.BaseNetworkObserver;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * This class tracks the data connection associated with Ethernet
- * This is a singleton class and an instance will be created by
- * ConnectivityService.
- * @hide
- */
-public class EthernetDataTracker extends BaseNetworkStateTracker {
- private static final String NETWORKTYPE = "ETHERNET";
- private static final String TAG = "Ethernet";
-
- private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
- private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
- private AtomicInteger mDefaultGatewayAddr = new AtomicInteger(0);
- private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
-
- private static boolean mLinkUp;
- private InterfaceObserver mInterfaceObserver;
- private String mHwAddr;
-
- /* For sending events to connectivity service handler */
- private Handler mCsHandler;
-
- private static EthernetDataTracker sInstance;
- private static String sIfaceMatch = "";
- private static String mIface = "";
-
- private INetworkManagementService mNMService;
-
- private static class InterfaceObserver extends BaseNetworkObserver {
- private EthernetDataTracker mTracker;
-
- InterfaceObserver(EthernetDataTracker tracker) {
- super();
- mTracker = tracker;
- }
-
- @Override
- public void interfaceStatusChanged(String iface, boolean up) {
- Log.d(TAG, "Interface status changed: " + iface + (up ? "up" : "down"));
- }
-
- @Override
- public void interfaceLinkStateChanged(String iface, boolean up) {
- if (mIface.equals(iface)) {
- Log.d(TAG, "Interface " + iface + " link " + (up ? "up" : "down"));
- mLinkUp = up;
- mTracker.mNetworkInfo.setIsAvailable(up);
-
- // use DHCP
- if (up) {
- mTracker.reconnect();
- } else {
- mTracker.disconnect();
- }
- }
- }
-
- @Override
- public void interfaceAdded(String iface) {
- mTracker.interfaceAdded(iface);
- }
-
- @Override
- public void interfaceRemoved(String iface) {
- mTracker.interfaceRemoved(iface);
- }
- }
-
- private EthernetDataTracker() {
- mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORKTYPE, "");
- mLinkProperties = new LinkProperties();
- 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;
-
- Log.d(TAG, "Adding " + iface);
-
- synchronized(this) {
- if(!mIface.isEmpty())
- return;
- mIface = iface;
- }
-
- interfaceUpdated();
-
- mNetworkInfo.setIsAvailable(true);
- Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
- msg.sendToTarget();
- }
-
- public void disconnect() {
-
- NetworkUtils.stopDhcp(mIface);
-
- mLinkProperties.clear();
- mNetworkInfo.setIsAvailable(false);
- mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
-
- Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
- msg.sendToTarget();
-
- msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
- msg.sendToTarget();
-
- IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
- INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
- try {
- service.clearInterfaceAddresses(mIface);
- } catch (Exception e) {
- Log.e(TAG, "Failed to clear addresses or disable ipv6" + e);
- }
- }
-
- private void interfaceRemoved(String iface) {
- if (!iface.equals(mIface))
- return;
-
- Log.d(TAG, "Removing " + iface);
- disconnect();
- 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;
-
- mNetworkInfo.setIsAvailable(true);
- mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr);
- Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
- msg.sendToTarget();
- }
- });
- dhcpThread.start();
- }
-
- public static synchronized EthernetDataTracker getInstance() {
- if (sInstance == null) sInstance = new EthernetDataTracker();
- return sInstance;
- }
-
- public Object Clone() throws CloneNotSupportedException {
- throw new CloneNotSupportedException();
- }
-
- public void setTeardownRequested(boolean isRequested) {
- mTeardownRequested.set(isRequested);
- }
-
- public boolean isTeardownRequested() {
- return mTeardownRequested.get();
- }
-
- /**
- * Begin monitoring connectivity
- */
- public void startMonitoring(Context context, Handler target) {
- mContext = context;
- mCsHandler = target;
-
- // register for notifications from NetworkManagement Service
- IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
- mNMService = INetworkManagementService.Stub.asInterface(b);
-
- mInterfaceObserver = new InterfaceObserver(this);
-
- // enable and try to connect to an ethernet interface that
- // already exists
- sIfaceMatch = context.getResources().getString(
- com.android.internal.R.string.config_ethernet_iface_regex);
- try {
- final String[] ifaces = mNMService.listInterfaces();
- for (String iface : ifaces) {
- if (iface.matches(sIfaceMatch)) {
- mIface = iface;
- interfaceUpdated();
-
- // if a DHCP client had previously been started for this interface, then stop it
- NetworkUtils.stopDhcp(mIface);
-
- reconnect();
- break;
- }
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Could not get list of interfaces " + e);
- }
-
- try {
- mNMService.registerObserver(mInterfaceObserver);
- } catch (RemoteException e) {
- Log.e(TAG, "Could not register InterfaceObserver " + e);
- }
- }
-
- /**
- * Disable connectivity to a network
- * TODO: do away with return value after making MobileDataStateTracker async
- */
- public boolean teardown() {
- mTeardownRequested.set(true);
- NetworkUtils.stopDhcp(mIface);
- return true;
- }
-
- /**
- * Re-enable connectivity to a network after a {@link #teardown()}.
- */
- public boolean reconnect() {
- if (mLinkUp) {
- mTeardownRequested.set(false);
- runDhcp();
- }
- return mLinkUp;
- }
-
- @Override
- public void captivePortalCheckCompleted(boolean isCaptivePortal) {
- // not implemented
- }
-
- /**
- * Turn the wireless radio off for a network.
- * @param turnOn {@code true} to turn the radio on, {@code false}
- */
- public boolean setRadio(boolean turnOn) {
- return true;
- }
-
- /**
- * @return true - If are we currently tethered with another device.
- */
- public synchronized boolean isAvailable() {
- return mNetworkInfo.isAvailable();
- }
-
- /**
- * Tells the underlying networking system that the caller wants to
- * begin using the named feature. The interpretation of {@code feature}
- * is completely up to each networking implementation.
- * @param feature the name of the feature to be used
- * @param callingPid the process ID of the process that is issuing this request
- * @param callingUid the user ID of the process that is issuing this request
- * @return an integer value representing the outcome of the request.
- * The interpretation of this value is specific to each networking
- * implementation+feature combination, except that the value {@code -1}
- * always indicates failure.
- * TODO: needs to go away
- */
- public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
- return -1;
- }
-
- /**
- * Tells the underlying networking system that the caller is finished
- * using the named feature. The interpretation of {@code feature}
- * is completely up to each networking implementation.
- * @param feature the name of the feature that is no longer needed.
- * @param callingPid the process ID of the process that is issuing this request
- * @param callingUid the user ID of the process that is issuing this request
- * @return an integer value representing the outcome of the request.
- * The interpretation of this value is specific to each networking
- * implementation+feature combination, except that the value {@code -1}
- * always indicates failure.
- * TODO: needs to go away
- */
- public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
- return -1;
- }
-
- @Override
- public void setUserDataEnable(boolean enabled) {
- Log.w(TAG, "ignoring setUserDataEnable(" + enabled + ")");
- }
-
- @Override
- public void setPolicyDataEnable(boolean enabled) {
- Log.w(TAG, "ignoring setPolicyDataEnable(" + enabled + ")");
- }
-
- /**
- * Check if private DNS route is set for the network
- */
- public boolean isPrivateDnsRouteSet() {
- return mPrivateDnsRouteSet.get();
- }
-
- /**
- * Set a flag indicating private DNS route is set
- */
- public void privateDnsRouteSet(boolean enabled) {
- mPrivateDnsRouteSet.set(enabled);
- }
-
- /**
- * Fetch NetworkInfo for the network
- */
- public synchronized NetworkInfo getNetworkInfo() {
- return mNetworkInfo;
- }
-
- /**
- * Fetch LinkProperties for the network
- */
- public synchronized LinkProperties getLinkProperties() {
- return new LinkProperties(mLinkProperties);
- }
-
- /**
- * A capability is an Integer/String pair, the capabilities
- * are defined in the class LinkSocket#Key.
- *
- * @return a copy of this connections capabilities, may be empty but never null.
- */
- public LinkCapabilities getLinkCapabilities() {
- return new LinkCapabilities(mLinkCapabilities);
- }
-
- /**
- * Fetch default gateway address for the network
- */
- public int getDefaultGatewayAddr() {
- return mDefaultGatewayAddr.get();
- }
-
- /**
- * Check if default route is set
- */
- public boolean isDefaultRouteSet() {
- return mDefaultRouteSet.get();
- }
-
- /**
- * Set a flag indicating default route is set for the network
- */
- public void defaultRouteSet(boolean enabled) {
- mDefaultRouteSet.set(enabled);
- }
-
- /**
- * Return the system properties name associated with the tcp buffer sizes
- * for this network.
- */
- public String getTcpBufferSizesPropName() {
- return "net.tcp.buffersize.ethernet";
- }
-
- public void setDependencyMet(boolean met) {
- // not supported on this network
- }
-
- @Override
- public void addStackedLink(LinkProperties link) {
- mLinkProperties.addStackedLink(link);
- }
-
- @Override
- public void removeStackedLink(LinkProperties link) {
- mLinkProperties.removeStackedLink(link);
- }
-
- @Override
- public void supplyMessenger(Messenger messenger) {
- // not supported on this network
- }
-
- @Override
- public String getNetworkInterfaceName() {
- return mIface;
- }
-}
diff --git a/core/java/android/net/EthernetManager.java b/core/java/android/net/EthernetManager.java
new file mode 100644
index 0000000..5df4baf
--- /dev/null
+++ b/core/java/android/net/EthernetManager.java
@@ -0,0 +1,72 @@
+/*
+ * 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.content.Context;
+import android.net.IEthernetManager;
+import android.net.IpConfiguration;
+import android.net.IpConfiguration.IpAssignment;
+import android.net.IpConfiguration.ProxySettings;
+import android.net.LinkProperties;
+import android.os.RemoteException;
+
+/**
+ * A class representing the IP configuration of the Ethernet network.
+ *
+ * @hide
+ */
+public class EthernetManager {
+ private static final String TAG = "EthernetManager";
+
+ private final Context mContext;
+ private final IEthernetManager mService;
+
+ /**
+ * Create a new EthernetManager instance.
+ * Applications will almost always want to use
+ * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
+ * the standard {@link android.content.Context#ETHERNET_SERVICE Context.ETHERNET_SERVICE}.
+ */
+ public EthernetManager(Context context, IEthernetManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Get Ethernet configuration.
+ * @return the Ethernet Configuration, contained in {@link IpConfiguration}.
+ */
+ public IpConfiguration getConfiguration() {
+ try {
+ return mService.getConfiguration();
+ } catch (RemoteException e) {
+ return new IpConfiguration(IpAssignment.UNASSIGNED,
+ ProxySettings.UNASSIGNED,
+ new LinkProperties());
+ }
+ }
+
+ /**
+ * Set Ethernet configuration.
+ */
+ public void setConfiguration(IpConfiguration config) {
+ try {
+ mService.setConfiguration(config);
+ } catch (RemoteException e) {
+ }
+ }
+}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index d53a856..5f1ff3e 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -16,10 +16,14 @@
package android.net;
+import android.app.PendingIntent;
import android.net.LinkQualityInfo;
import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkQuotaInfo;
+import android.net.NetworkRequest;
import android.net.NetworkState;
import android.net.ProxyInfo;
import android.os.IBinder;
@@ -41,10 +45,6 @@ interface IConnectivityManager
// Keep this in sync with framework/native/services/connectivitymanager/ConnectivityManager.h
void markSocketAsUser(in ParcelFileDescriptor socket, int uid);
- void setNetworkPreference(int pref);
-
- int getNetworkPreference();
-
NetworkInfo getActiveNetworkInfo();
NetworkInfo getActiveNetworkInfoForUid(int uid);
NetworkInfo getNetworkInfo(int networkType);
@@ -55,17 +55,16 @@ interface IConnectivityManager
boolean isNetworkSupported(int networkType);
LinkProperties getActiveLinkProperties();
- LinkProperties getLinkProperties(int networkType);
+ LinkProperties getLinkPropertiesForType(int networkType);
+ LinkProperties getLinkProperties(in Network network);
+
+ NetworkCapabilities getNetworkCapabilities(in Network network);
NetworkState[] getAllNetworkState();
NetworkQuotaInfo getActiveNetworkQuotaInfo();
boolean isActiveNetworkMetered();
- boolean setRadios(boolean onOff);
-
- boolean setRadio(int networkType, boolean turnOn);
-
int startUsingNetworkFeature(int networkType, in String feature,
in IBinder binder);
@@ -75,9 +74,6 @@ interface IConnectivityManager
boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress, String packageName);
- boolean getMobileDataEnabled();
- void setMobileDataEnabled(boolean enabled);
-
/** Policy control over specific {@link NetworkStateTracker}. */
void setPolicyDataEnable(int networkType, boolean enabled);
@@ -107,6 +103,8 @@ interface IConnectivityManager
void reportInetCondition(int networkType, int percentage);
+ void reportBadNetwork(in Network network);
+
ProxyInfo getGlobalProxy();
void setGlobalProxy(in ProxyInfo p);
@@ -147,7 +145,31 @@ interface IConnectivityManager
LinkQualityInfo[] getAllLinkQualityInfo();
- void setProvisioningNotificationVisible(boolean visible, int networkType, in String extraInfo, in String url);
+ void setProvisioningNotificationVisible(boolean visible, int networkType, in String extraInfo,
+ in String url);
void setAirplaneMode(boolean enable);
+
+ void registerNetworkFactory(in Messenger messenger, in String name);
+
+ void unregisterNetworkFactory(in Messenger messenger);
+
+ void registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp,
+ in NetworkCapabilities nc, int score);
+
+ NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities,
+ in Messenger messenger, int timeoutSec, in IBinder binder, int legacy);
+
+ NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities,
+ in PendingIntent operation);
+
+ NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities,
+ in Messenger messenger, in IBinder binder);
+
+ void pendingListenForNetwork(in NetworkCapabilities networkCapabilities,
+ in PendingIntent operation);
+
+ void releaseNetworkRequest(in NetworkRequest networkRequest);
+
+ int getRestoreDefaultNetworkDelay(int networkType);
}
diff --git a/core/java/android/net/IEthernetManager.aidl b/core/java/android/net/IEthernetManager.aidl
new file mode 100644
index 0000000..3fa08f8
--- /dev/null
+++ b/core/java/android/net/IEthernetManager.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.net;
+
+import android.net.IpConfiguration;
+
+/**
+ * Interface that answers queries about, and allows changing
+ * ethernet configuration.
+ */
+/** {@hide} */
+interface IEthernetManager
+{
+ IpConfiguration getConfiguration();
+ void setConfiguration(in IpConfiguration config);
+}
diff --git a/core/java/android/net/IpConfiguration.aidl b/core/java/android/net/IpConfiguration.aidl
new file mode 100644
index 0000000..7a30f0e
--- /dev/null
+++ b/core/java/android/net/IpConfiguration.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 IpConfiguration;
diff --git a/core/java/android/net/IpConfiguration.java b/core/java/android/net/IpConfiguration.java
new file mode 100644
index 0000000..4730bab
--- /dev/null
+++ b/core/java/android/net/IpConfiguration.java
@@ -0,0 +1,151 @@
+/*
+ * 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.LinkProperties;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A class representing a configured network.
+ * @hide
+ */
+public class IpConfiguration implements Parcelable {
+ private static final String TAG = "IpConfiguration";
+
+ public enum IpAssignment {
+ /* Use statically configured IP settings. Configuration can be accessed
+ * with linkProperties */
+ STATIC,
+ /* Use dynamically configured IP settigns */
+ DHCP,
+ /* no IP details are assigned, this is used to indicate
+ * that any existing IP settings should be retained */
+ UNASSIGNED
+ }
+
+ public IpAssignment ipAssignment;
+
+ public enum ProxySettings {
+ /* No proxy is to be used. Any existing proxy settings
+ * should be cleared. */
+ NONE,
+ /* Use statically configured proxy. Configuration can be accessed
+ * with linkProperties */
+ STATIC,
+ /* no proxy details are assigned, this is used to indicate
+ * that any existing proxy settings should be retained */
+ UNASSIGNED,
+ /* Use a Pac based proxy.
+ */
+ PAC
+ }
+
+ public ProxySettings proxySettings;
+
+ public LinkProperties linkProperties;
+
+ public IpConfiguration(IpConfiguration source) {
+ if (source != null) {
+ ipAssignment = source.ipAssignment;
+ proxySettings = source.proxySettings;
+ linkProperties = new LinkProperties(source.linkProperties);
+ } else {
+ ipAssignment = IpAssignment.UNASSIGNED;
+ proxySettings = ProxySettings.UNASSIGNED;
+ linkProperties = new LinkProperties();
+ }
+ }
+
+ public IpConfiguration() {
+ this(null);
+ }
+
+ public IpConfiguration(IpAssignment ipAssignment,
+ ProxySettings proxySettings,
+ LinkProperties linkProperties) {
+ this.ipAssignment = ipAssignment;
+ this.proxySettings = proxySettings;
+ this.linkProperties = new LinkProperties(linkProperties);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sbuf = new StringBuilder();
+ sbuf.append("IP assignment: " + ipAssignment.toString());
+ sbuf.append("\n");
+ sbuf.append("Proxy settings: " + proxySettings.toString());
+ sbuf.append("\n");
+ sbuf.append(linkProperties.toString());
+ sbuf.append("\n");
+
+ return sbuf.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (!(o instanceof IpConfiguration)) {
+ return false;
+ }
+
+ IpConfiguration other = (IpConfiguration) o;
+ return this.ipAssignment == other.ipAssignment &&
+ this.proxySettings == other.proxySettings &&
+ Objects.equals(this.linkProperties, other.linkProperties);
+ }
+
+ @Override
+ public int hashCode() {
+ return 13 + (linkProperties != null ? linkProperties.hashCode() : 0) +
+ 17 * ipAssignment.ordinal() +
+ 47 * proxySettings.ordinal();
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(ipAssignment.name());
+ dest.writeString(proxySettings.name());
+ dest.writeParcelable(linkProperties, flags);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final Creator<IpConfiguration> CREATOR =
+ new Creator<IpConfiguration>() {
+ public IpConfiguration createFromParcel(Parcel in) {
+ IpConfiguration config = new IpConfiguration();
+ config.ipAssignment = IpAssignment.valueOf(in.readString());
+ config.proxySettings = ProxySettings.valueOf(in.readString());
+ config.linkProperties = in.readParcelable(null);
+ return config;
+ }
+
+ public IpConfiguration[] newArray(int size) {
+ return new IpConfiguration[size];
+ }
+ };
+}
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
index a725bec..d07c0b61 100644
--- a/core/java/android/net/LinkAddress.java
+++ b/core/java/android/net/LinkAddress.java
@@ -39,7 +39,8 @@ import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
* <ul>
* <li>An IP address and prefix length (e.g., {@code 2001:db8::1/64} or {@code 192.0.2.1/24}).
* The address must be unicast, as multicast addresses cannot be assigned to interfaces.
- * <li>Address flags: A bitmask of {@code IFA_F_*} values representing properties of the address.
+ * <li>Address flags: A bitmask of {@code IFA_F_*} values representing properties
+ * of the address.
* <li>Address scope: An integer defining the scope in which the address is unique (e.g.,
* {@code RT_SCOPE_LINK} or {@code RT_SCOPE_SITE}).
* <ul>
@@ -47,10 +48,9 @@ import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
* When constructing a {@code LinkAddress}, the IP address and prefix are required. The flags and
* scope are optional. If they are not specified, the flags are set to zero, and the scope will be
* determined based on the IP address (e.g., link-local addresses will be created with a scope of
- * {@code RT_SCOPE_LINK}, global addresses with {@code RT_SCOPE_UNIVERSE}, etc.) If they are
- * specified, they are not checked for validity.
+ * {@code RT_SCOPE_LINK}, global addresses with {@code RT_SCOPE_UNIVERSE},
+ * etc.) If they are specified, they are not checked for validity.
*
- * @hide
*/
public class LinkAddress implements Parcelable {
/**
@@ -119,6 +119,10 @@ public class LinkAddress implements Parcelable {
* the specified flags and scope. Flags and scope are not checked for validity.
* @param address The IP address.
* @param prefixLength The prefix length.
+ * @param flags A bitmask of {@code IFA_F_*} values representing properties of the address.
+ * @param scope An integer defining the scope in which the address is unique (e.g.,
+ * {@link OsConstants#RT_SCOPE_LINK} or {@link OsConstants#RT_SCOPE_SITE}).
+ * @hide
*/
public LinkAddress(InetAddress address, int prefixLength, int flags, int scope) {
init(address, prefixLength, flags, scope);
@@ -129,6 +133,7 @@ public class LinkAddress implements Parcelable {
* The flags are set to zero and the scope is determined from the address.
* @param address The IP address.
* @param prefixLength The prefix length.
+ * @hide
*/
public LinkAddress(InetAddress address, int prefixLength) {
this(address, prefixLength, 0, 0);
@@ -139,6 +144,7 @@ public class LinkAddress implements Parcelable {
* Constructs a new {@code LinkAddress} from an {@code InterfaceAddress}.
* The flags are set to zero and the scope is determined from the address.
* @param interfaceAddress The interface address.
+ * @hide
*/
public LinkAddress(InterfaceAddress interfaceAddress) {
this(interfaceAddress.getAddress(),
@@ -149,6 +155,7 @@ public class LinkAddress implements Parcelable {
* Constructs a new {@code LinkAddress} from a string such as "192.0.2.5/24" or
* "2001:db8::1/64". The flags are set to zero and the scope is determined from the address.
* @param string The string to parse.
+ * @hide
*/
public LinkAddress(String address) {
this(address, 0, 0);
@@ -161,6 +168,7 @@ public class LinkAddress implements Parcelable {
* @param string The string to parse.
* @param flags The address flags.
* @param scope The address scope.
+ * @hide
*/
public LinkAddress(String address, int flags, int scope) {
InetAddress inetAddress = null;
@@ -220,9 +228,10 @@ public class LinkAddress implements Parcelable {
}
/**
- * Determines whether this {@code LinkAddress} and the provided {@code LinkAddress} represent
- * the same address. Two LinkAddresses represent the same address if they have the same IP
- * address and prefix length, even if their properties are different.
+ * Determines whether this {@code LinkAddress} and the provided {@code LinkAddress}
+ * represent the same address. Two {@code LinkAddresses} represent the same address
+ * if they have the same IP address and prefix length, even if their properties are
+ * different.
*
* @param other the {@code LinkAddress} to compare to.
* @return {@code true} if both objects have the same address and prefix length, {@code false}
@@ -233,28 +242,28 @@ public class LinkAddress implements Parcelable {
}
/**
- * Returns the InetAddress of this address.
+ * Returns the {@link InetAddress} of this {@code LinkAddress}.
*/
public InetAddress getAddress() {
return address;
}
/**
- * Returns the prefix length of this address.
+ * Returns the prefix length of this {@code LinkAddress}.
*/
public int getNetworkPrefixLength() {
return prefixLength;
}
/**
- * Returns the flags of this address.
+ * Returns the flags of this {@code LinkAddress}.
*/
public int getFlags() {
return flags;
}
/**
- * Returns the scope of this address.
+ * Returns the scope of this {@code LinkAddress}.
*/
public int getScope() {
return scope;
@@ -262,6 +271,7 @@ public class LinkAddress implements Parcelable {
/**
* Returns true if this {@code LinkAddress} is global scope and preferred.
+ * @hide
*/
public boolean isGlobalPreferred() {
return (scope == RT_SCOPE_UNIVERSE &&
diff --git a/core/java/android/net/LinkCapabilities.java b/core/java/android/net/LinkCapabilities.java
deleted file mode 100644
index fb444ea..0000000
--- a/core/java/android/net/LinkCapabilities.java
+++ /dev/null
@@ -1,362 +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.net;
-
-import android.os.Parcelable;
-import android.os.Parcel;
-import android.util.Log;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-/**
- * A class representing the capabilities of a link
- *
- * @hide
- */
-public class LinkCapabilities implements Parcelable {
- private static final String TAG = "LinkCapabilities";
- private static final boolean DBG = false;
-
- /** The Map of Keys to Values */
- private HashMap<Integer, String> mCapabilities;
-
-
- /**
- * The set of keys defined for a links capabilities.
- *
- * Keys starting with RW are read + write, i.e. the application
- * can request for a certain requirement corresponding to that key.
- * Keys starting with RO are read only, i.e. the the application
- * can read the value of that key from the socket but cannot request
- * a corresponding requirement.
- *
- * TODO: Provide a documentation technique for concisely and precisely
- * define the syntax for each value string associated with a key.
- */
- public static final class Key {
- /** No constructor */
- private Key() {}
-
- /**
- * An integer representing the network type.
- * @see ConnectivityManager
- */
- public final static int RO_NETWORK_TYPE = 1;
-
- /**
- * Desired minimum forward link (download) bandwidth for the
- * in kilobits per second (kbps). Values should be strings such
- * "50", "100", "1500", etc.
- */
- public final static int RW_DESIRED_FWD_BW = 2;
-
- /**
- * Required minimum forward link (download) bandwidth, in
- * per second (kbps), below which the socket cannot function.
- * Values should be strings such as "50", "100", "1500", etc.
- */
- public final static int RW_REQUIRED_FWD_BW = 3;
-
- /**
- * Available forward link (download) bandwidth for the socket.
- * This value is in kilobits per second (kbps).
- * Values will be strings such as "50", "100", "1500", etc.
- */
- public final static int RO_AVAILABLE_FWD_BW = 4;
-
- /**
- * Desired minimum reverse link (upload) bandwidth for the socket
- * in kilobits per second (kbps).
- * Values should be strings such as "50", "100", "1500", etc.
- * <p>
- * This key is set via the needs map.
- */
- public final static int RW_DESIRED_REV_BW = 5;
-
- /**
- * Required minimum reverse link (upload) bandwidth, in kilobits
- * per second (kbps), below which the socket cannot function.
- * If a rate is not specified, the default rate of kbps will be
- * Values should be strings such as "50", "100", "1500", etc.
- */
- public final static int RW_REQUIRED_REV_BW = 6;
-
- /**
- * Available reverse link (upload) bandwidth for the socket.
- * This value is in kilobits per second (kbps).
- * Values will be strings such as "50", "100", "1500", etc.
- */
- public final static int RO_AVAILABLE_REV_BW = 7;
-
- /**
- * Maximum latency for the socket, in milliseconds, above which
- * socket cannot function.
- * Values should be strings such as "50", "300", "500", etc.
- */
- public final static int RW_MAX_ALLOWED_LATENCY = 8;
-
- /**
- * Interface that the socket is bound to. This can be a virtual
- * interface (e.g. VPN or Mobile IP) or a physical interface
- * (e.g. wlan0 or rmnet0).
- * Values will be strings such as "wlan0", "rmnet0"
- */
- public final static int RO_BOUND_INTERFACE = 9;
-
- /**
- * Physical interface that the socket is routed on.
- * This can be different from BOUND_INTERFACE in cases such as
- * VPN or Mobile IP. The physical interface may change over time
- * if seamless mobility is supported.
- * Values will be strings such as "wlan0", "rmnet0"
- */
- public final static int RO_PHYSICAL_INTERFACE = 10;
- }
-
- /**
- * Role informs the LinkSocket about the data usage patterns of your
- * application.
- * <P>
- * {@code Role.DEFAULT} is the default role, and is used whenever
- * a role isn't set.
- */
- public static final class Role {
- /** No constructor */
- private Role() {}
-
- // examples only, discuss which roles should be defined, and then
- // code these to match
-
- /** Default Role */
- public static final String DEFAULT = "default";
- /** Bulk down load */
- public static final String BULK_DOWNLOAD = "bulk.download";
- /** Bulk upload */
- public static final String BULK_UPLOAD = "bulk.upload";
-
- /** VoIP Application at 24kbps */
- public static final String VOIP_24KBPS = "voip.24k";
- /** VoIP Application at 32kbps */
- public static final String VOIP_32KBPS = "voip.32k";
-
- /** Video Streaming at 480p */
- public static final String VIDEO_STREAMING_480P = "video.streaming.480p";
- /** Video Streaming at 720p */
- public static final String VIDEO_STREAMING_720I = "video.streaming.720i";
-
- /** Video Chat Application at 360p */
- public static final String VIDEO_CHAT_360P = "video.chat.360p";
- /** Video Chat Application at 480p */
- public static final String VIDEO_CHAT_480P = "video.chat.480i";
- }
-
- /**
- * Constructor
- */
- public LinkCapabilities() {
- mCapabilities = new HashMap<Integer, String>();
- }
-
- /**
- * Copy constructor.
- *
- * @param source
- */
- public LinkCapabilities(LinkCapabilities source) {
- if (source != null) {
- mCapabilities = new HashMap<Integer, String>(source.mCapabilities);
- } else {
- mCapabilities = new HashMap<Integer, String>();
- }
- }
-
- /**
- * Create the {@code LinkCapabilities} with values depending on role type.
- * @param applicationRole a {@code LinkSocket.Role}
- * @return the {@code LinkCapabilities} associated with the applicationRole, empty if none
- */
- public static LinkCapabilities createNeedsMap(String applicationRole) {
- if (DBG) log("createNeededCapabilities(applicationRole) EX");
- return new LinkCapabilities();
- }
-
- /**
- * Remove all capabilities
- */
- public void clear() {
- mCapabilities.clear();
- }
-
- /**
- * Returns whether this map is empty.
- */
- public boolean isEmpty() {
- return mCapabilities.isEmpty();
- }
-
- /**
- * Returns the number of elements in this map.
- *
- * @return the number of elements in this map.
- */
- public int size() {
- return mCapabilities.size();
- }
-
- /**
- * Given the key return the capability string
- *
- * @param key
- * @return the capability string
- */
- public String get(int key) {
- return mCapabilities.get(key);
- }
-
- /**
- * Store the key/value capability pair
- *
- * @param key
- * @param value
- */
- public void put(int key, String value) {
- mCapabilities.put(key, value);
- }
-
- /**
- * Returns whether this map contains the specified key.
- *
- * @param key to search for.
- * @return {@code true} if this map contains the specified key,
- * {@code false} otherwise.
- */
- public boolean containsKey(int key) {
- return mCapabilities.containsKey(key);
- }
-
- /**
- * Returns whether this map contains the specified value.
- *
- * @param value to search for.
- * @return {@code true} if this map contains the specified value,
- * {@code false} otherwise.
- */
- public boolean containsValue(String value) {
- return mCapabilities.containsValue(value);
- }
-
- /**
- * Returns a set containing all of the mappings in this map. Each mapping is
- * an instance of {@link Map.Entry}. As the set is backed by this map,
- * changes in one will be reflected in the other.
- *
- * @return a set of the mappings.
- */
- public Set<Entry<Integer, String>> entrySet() {
- return mCapabilities.entrySet();
- }
-
- /**
- * @return the set of the keys.
- */
- public Set<Integer> keySet() {
- return mCapabilities.keySet();
- }
-
- /**
- * @return the set of values
- */
- public Collection<String> values() {
- return mCapabilities.values();
- }
-
- /**
- * Implement the Parcelable interface
- * @hide
- */
- public int describeContents() {
- return 0;
- }
-
- /**
- * Convert to string for debugging
- */
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("{");
- boolean firstTime = true;
- for (Entry<Integer, String> entry : mCapabilities.entrySet()) {
- if (firstTime) {
- firstTime = false;
- } else {
- sb.append(",");
- }
- sb.append(entry.getKey());
- sb.append(":\"");
- sb.append(entry.getValue());
- sb.append("\"");
- }
- sb.append("}");
- return sb.toString();
- }
-
- /**
- * Implement the Parcelable interface.
- * @hide
- */
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mCapabilities.size());
- for (Entry<Integer, String> entry : mCapabilities.entrySet()) {
- dest.writeInt(entry.getKey().intValue());
- dest.writeString(entry.getValue());
- }
- }
-
- /**
- * Implement the Parcelable interface.
- * @hide
- */
- public static final Creator<LinkCapabilities> CREATOR =
- new Creator<LinkCapabilities>() {
- public LinkCapabilities createFromParcel(Parcel in) {
- LinkCapabilities capabilities = new LinkCapabilities();
- int size = in.readInt();
- while (size-- != 0) {
- int key = in.readInt();
- String value = in.readString();
- capabilities.mCapabilities.put(key, value);
- }
- return capabilities;
- }
-
- public LinkCapabilities[] newArray(int size) {
- return new LinkCapabilities[size];
- }
- };
-
- /**
- * Debug logging
- */
- protected static void log(String s) {
- Log.d(TAG, s);
- }
-}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 2dcc544..3c36679 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -36,27 +36,12 @@ import java.util.Hashtable;
*
* A link represents a connection to a network.
* It may have multiple addresses and multiple gateways,
- * multiple dns servers but only one http proxy.
+ * multiple dns servers but only one http proxy and one
+ * network interface.
*
- * Because it's a single network, the dns's
- * are interchangeable and don't need associating with
- * particular addresses. The gateways similarly don't
- * need associating with particular addresses.
+ * Note that this is just a holder of data. Modifying it
+ * does not affect live networks.
*
- * A dual stack interface works fine in this model:
- * each address has it's own prefix length to describe
- * the local network. The dns servers all return
- * both v4 addresses and v6 addresses regardless of the
- * address family of the server itself (rfc4213) and we
- * don't care which is used. The gateways will be
- * selected based on the destination address and the
- * source address has no relavence.
- *
- * Links can also be stacked on top of each other.
- * This can be used, for example, to represent a tunnel
- * interface that runs on top of a physical interface.
- *
- * @hide
*/
public class LinkProperties implements Parcelable {
// The interface described by the network link.
@@ -73,6 +58,7 @@ public class LinkProperties implements Parcelable {
private Hashtable<String, LinkProperties> mStackedLinks =
new Hashtable<String, LinkProperties>();
+ // @hide
public static class CompareResult<T> {
public Collection<T> removed = new ArrayList<T>();
public Collection<T> added = new ArrayList<T>();
@@ -89,10 +75,8 @@ public class LinkProperties implements Parcelable {
}
public LinkProperties() {
- clear();
}
- // copy constructor instead of clone
public LinkProperties(LinkProperties source) {
if (source != null) {
mIfaceName = source.getInterfaceName();
@@ -109,6 +93,12 @@ public class LinkProperties implements Parcelable {
}
}
+ /**
+ * Sets the interface name for this link. All {@link RouteInfo} already set for this
+ * will have their interface changed to match this new value.
+ *
+ * @param iface The name of the network interface used for this link.
+ */
public void setInterfaceName(String iface) {
mIfaceName = iface;
ArrayList<RouteInfo> newRoutes = new ArrayList<RouteInfo>(mRoutes.size());
@@ -118,10 +108,16 @@ public class LinkProperties implements Parcelable {
mRoutes = newRoutes;
}
+ /**
+ * Gets the interface name for this link. May be {@code null} if not set.
+ *
+ * @return The interface name set for this link or {@code null}.
+ */
public String getInterfaceName() {
return mIfaceName;
}
+ // @hide
public Collection<String> getAllInterfaceNames() {
Collection interfaceNames = new ArrayList<String>(mStackedLinks.size() + 1);
if (mIfaceName != null) interfaceNames.add(new String(mIfaceName));
@@ -132,7 +128,14 @@ public class LinkProperties implements Parcelable {
}
/**
- * Returns all the addresses on this link.
+ * Returns all the addresses on this link. We often think of a link having a single address,
+ * however, particularly with Ipv6 several addresses are typical. Note that the
+ * {@code LinkProperties} actually contains {@link LinkAddress} objects which also include
+ * prefix lengths for each address. This is a simplified utility alternative to
+ * {@link LinkProperties#getLinkAddresses}.
+ *
+ * @return An umodifiable {@link Collection} of {@link InetAddress} for this link.
+ * @hide
*/
public Collection<InetAddress> getAddresses() {
Collection<InetAddress> addresses = new ArrayList<InetAddress>();
@@ -144,6 +147,7 @@ public class LinkProperties implements Parcelable {
/**
* Returns all the addresses on this link and all the links stacked above it.
+ * @hide
*/
public Collection<InetAddress> getAllAddresses() {
Collection<InetAddress> addresses = new ArrayList<InetAddress>();
@@ -166,7 +170,8 @@ public class LinkProperties implements Parcelable {
}
/**
- * Adds a link address if it does not exist, or updates it if it does.
+ * Adds a {@link LinkAddress} to this {@code LinkProperties} if a {@link LinkAddress} of the
+ * same address/prefix does not already exist. If it does exist it is replaced.
* @param address The {@code LinkAddress} to add.
* @return true if {@code address} was added or updated, false otherwise.
*/
@@ -190,9 +195,10 @@ public class LinkProperties implements Parcelable {
}
/**
- * Removes a link address. Specifically, removes the link address, if any, for which
- * {@code isSameAddressAs(toRemove)} returns true.
- * @param address A {@code LinkAddress} specifying the address to remove.
+ * Removes a {@link LinkAddress} from this {@code LinkProperties}. Specifically, matches
+ * and {@link LinkAddress} with the same address and prefix.
+ *
+ * @param toRemove A {@link LinkAddress} specifying the address to remove.
* @return true if the address was removed, false if it did not exist.
*/
public boolean removeLinkAddress(LinkAddress toRemove) {
@@ -205,7 +211,10 @@ public class LinkProperties implements Parcelable {
}
/**
- * Returns all the addresses on this link.
+ * Returns all the {@link LinkAddress} on this link. Typically a link will have
+ * one IPv4 address and one or more IPv6 addresses.
+ *
+ * @return An unmodifiable {@link Collection} of {@link LinkAddress} for this link.
*/
public Collection<LinkAddress> getLinkAddresses() {
return Collections.unmodifiableCollection(mLinkAddresses);
@@ -213,6 +222,7 @@ public class LinkProperties implements Parcelable {
/**
* Returns all the addresses on this link and all the links stacked above it.
+ * @hide
*/
public Collection<LinkAddress> getAllLinkAddresses() {
Collection<LinkAddress> addresses = new ArrayList<LinkAddress>();
@@ -224,7 +234,11 @@ public class LinkProperties implements Parcelable {
}
/**
- * Replaces the LinkAddresses on this link with the given collection of addresses.
+ * Replaces the {@link LinkAddress} in this {@code LinkProperties} with
+ * the given {@link Collection} of {@link LinkAddress}.
+ *
+ * @param addresses The {@link Collection} of {@link LinkAddress} to set in this
+ * object.
*/
public void setLinkAddresses(Collection<LinkAddress> addresses) {
mLinkAddresses.clear();
@@ -233,26 +247,64 @@ public class LinkProperties implements Parcelable {
}
}
+ /**
+ * Adds the given {@link InetAddress} to the list of DNS servers.
+ *
+ * @param dns The {@link InetAddress} to add to the list of DNS servers.
+ */
public void addDns(InetAddress dns) {
if (dns != null) mDnses.add(dns);
}
+ /**
+ * Returns all the {@link LinkAddress} for DNS servers on this link.
+ *
+ * @return An umodifiable {@link Collection} of {@link InetAddress} for DNS servers on
+ * this link.
+ */
public Collection<InetAddress> getDnses() {
return Collections.unmodifiableCollection(mDnses);
}
- public String getDomains() {
- return mDomains;
- }
-
+ /**
+ * Sets the DNS domain search path used on this link.
+ *
+ * @param domains A {@link String} listing in priority order the comma separated
+ * domains to search when resolving host names on this link.
+ */
public void setDomains(String domains) {
mDomains = domains;
}
+ /**
+ * Get the DNS domains search path set for this link.
+ *
+ * @return A {@link String} containing the comma separated domains to search when resolving
+ * host names on this link.
+ */
+ public String getDomains() {
+ return mDomains;
+ }
+
+ /**
+ * Sets the Maximum Transmission Unit size to use on this link. This should not be used
+ * unless the system default (1500) is incorrect. Values less than 68 or greater than
+ * 10000 will be ignored.
+ *
+ * @param mtu The MTU to use for this link.
+ * @hide
+ */
public void setMtu(int mtu) {
mMtu = mtu;
}
+ /**
+ * Gets any non-default MTU size set for this link. Note that if the default is being used
+ * this will return 0.
+ *
+ * @return The mtu value set for this link.
+ * @hide
+ */
public int getMtu() {
return mMtu;
}
@@ -264,6 +316,14 @@ public class LinkProperties implements Parcelable {
mIfaceName);
}
+ /**
+ * Adds a {@link RouteInfo} to this {@code LinkProperties}. If the {@link RouteInfo}
+ * had an interface name set and that differs from the interface set for this
+ * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown. The
+ * proper course is to add either un-named or properly named {@link RouteInfo}.
+ *
+ * @param route A {@link RouteInfo} to add to this object.
+ */
public void addRoute(RouteInfo route) {
if (route != null) {
String routeIface = route.getInterface();
@@ -277,7 +337,9 @@ public class LinkProperties implements Parcelable {
}
/**
- * Returns all the routes on this link.
+ * Returns all the {@link RouteInfo} set on this link.
+ *
+ * @return An unmodifiable {@link Collection} of {@link RouteInfo} for this link.
*/
public Collection<RouteInfo> getRoutes() {
return Collections.unmodifiableCollection(mRoutes);
@@ -285,6 +347,7 @@ public class LinkProperties implements Parcelable {
/**
* Returns all the routes on this link and all the links stacked above it.
+ * @hide
*/
public Collection<RouteInfo> getAllRoutes() {
Collection<RouteInfo> routes = new ArrayList();
@@ -295,9 +358,22 @@ public class LinkProperties implements Parcelable {
return routes;
}
+ /**
+ * Sets the recommended {@link ProxyInfo} to use on this link, or {@code null} for none.
+ * Note that Http Proxies are only a hint - the system recommends their use, but it does
+ * not enforce it and applications may ignore them.
+ *
+ * @param proxy A {@link ProxyInfo} defining the Http Proxy to use on this link.
+ */
public void setHttpProxy(ProxyInfo proxy) {
mHttpProxy = proxy;
}
+
+ /**
+ * Gets the recommended {@link ProxyInfo} (or {@code null}) set on this link.
+ *
+ * @return The {@link ProxyInfo} set on this link
+ */
public ProxyInfo getHttpProxy() {
return mHttpProxy;
}
@@ -311,6 +387,7 @@ public class LinkProperties implements Parcelable {
*
* @param link The link to add.
* @return true if the link was stacked, false otherwise.
+ * @hide
*/
public boolean addStackedLink(LinkProperties link) {
if (link != null && link.getInterfaceName() != null) {
@@ -328,6 +405,7 @@ public class LinkProperties implements Parcelable {
*
* @param link The link to remove.
* @return true if the link was removed, false otherwise.
+ * @hide
*/
public boolean removeStackedLink(LinkProperties link) {
if (link != null && link.getInterfaceName() != null) {
@@ -339,6 +417,7 @@ public class LinkProperties implements Parcelable {
/**
* Returns all the links stacked on top of this link.
+ * @hide
*/
public Collection<LinkProperties> getStackedLinks() {
Collection<LinkProperties> stacked = new ArrayList<LinkProperties>();
@@ -348,6 +427,9 @@ public class LinkProperties implements Parcelable {
return Collections.unmodifiableCollection(stacked);
}
+ /**
+ * Clears this object to its initial state.
+ */
public void clear() {
mIfaceName = null;
mLinkAddresses.clear();
@@ -433,6 +515,7 @@ public class LinkProperties implements Parcelable {
*
* @param target LinkProperties to compare.
* @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
*/
public boolean isIdenticalInterfaceName(LinkProperties target) {
return TextUtils.equals(getInterfaceName(), target.getInterfaceName());
@@ -443,6 +526,7 @@ public class LinkProperties implements Parcelable {
*
* @param target LinkProperties to compare.
* @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
*/
public boolean isIdenticalAddresses(LinkProperties target) {
Collection<InetAddress> targetAddresses = target.getAddresses();
@@ -456,6 +540,7 @@ public class LinkProperties implements Parcelable {
*
* @param target LinkProperties to compare.
* @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
*/
public boolean isIdenticalDnses(LinkProperties target) {
Collection<InetAddress> targetDnses = target.getDnses();
@@ -474,6 +559,7 @@ public class LinkProperties implements Parcelable {
*
* @param target LinkProperties to compare.
* @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
*/
public boolean isIdenticalRoutes(LinkProperties target) {
Collection<RouteInfo> targetRoutes = target.getRoutes();
@@ -486,6 +572,7 @@ public class LinkProperties implements Parcelable {
*
* @param target LinkProperties to compare.
* @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
*/
public boolean isIdenticalHttpProxy(LinkProperties target) {
return getHttpProxy() == null ? target.getHttpProxy() == null :
@@ -497,6 +584,7 @@ public class LinkProperties implements Parcelable {
*
* @param target LinkProperties to compare.
* @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
*/
public boolean isIdenticalStackedLinks(LinkProperties target) {
if (!mStackedLinks.keySet().equals(target.mStackedLinks.keySet())) {
@@ -517,6 +605,7 @@ public class LinkProperties implements Parcelable {
*
* @param target LinkProperties to compare.
* @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
*/
public boolean isIdenticalMtu(LinkProperties target) {
return getMtu() == target.getMtu();
@@ -534,10 +623,6 @@ public class LinkProperties implements Parcelable {
* 1. Duplicated elements. eg, (A, B, B) and (A, A, B) are equal.
* 2. Worst case performance is O(n^2).
*
- * This method does not check that stacked interfaces are equal, because
- * stacked interfaces are not so much a property of the link as a
- * description of connections between links.
- *
* @param obj the object to be tested for equality.
* @return {@code true} if both objects are equal, {@code false} otherwise.
*/
@@ -547,7 +632,11 @@ public class LinkProperties implements Parcelable {
if (!(obj instanceof LinkProperties)) return false;
LinkProperties target = (LinkProperties) obj;
-
+ /**
+ * This method does not check that stacked interfaces are equal, because
+ * stacked interfaces are not so much a property of the link as a
+ * description of connections between links.
+ */
return isIdenticalInterfaceName(target) &&
isIdenticalAddresses(target) &&
isIdenticalDnses(target) &&
@@ -563,6 +652,7 @@ public class LinkProperties implements Parcelable {
*
* @param target a LinkProperties with the new list of addresses
* @return the differences between the addresses.
+ * @hide
*/
public CompareResult<LinkAddress> compareAddresses(LinkProperties target) {
/*
@@ -591,6 +681,7 @@ public class LinkProperties implements Parcelable {
*
* @param target a LinkProperties with the new list of dns addresses
* @return the differences between the DNS addresses.
+ * @hide
*/
public CompareResult<InetAddress> compareDnses(LinkProperties target) {
/*
@@ -620,6 +711,7 @@ public class LinkProperties implements Parcelable {
*
* @param target a LinkProperties with the new list of routes
* @return the differences between the routes.
+ * @hide
*/
public CompareResult<RouteInfo> compareAllRoutes(LinkProperties target) {
/*
@@ -642,6 +734,35 @@ public class LinkProperties implements Parcelable {
return result;
}
+ /**
+ * Compares all interface names in this LinkProperties with another
+ * LinkProperties, examining both the the base link and all stacked links.
+ *
+ * @param target a LinkProperties with the new list of interface names
+ * @return the differences between the interface names.
+ * @hide
+ */
+ public CompareResult<String> compareAllInterfaceNames(LinkProperties target) {
+ /*
+ * Duplicate the interface names into removed, we will be removing
+ * interface names which are common between this and target
+ * leaving the interface names that are different. And interface names which
+ * are in target but not in this are placed in added.
+ */
+ CompareResult<String> result = new CompareResult<String>();
+
+ result.removed = getAllInterfaceNames();
+ result.added.clear();
+ if (target != null) {
+ for (String r : target.getAllInterfaceNames()) {
+ if (! result.removed.remove(r)) {
+ result.added.add(r);
+ }
+ }
+ }
+ return result;
+ }
+
@Override
/**
diff --git a/core/java/android/net/LinkSocket.java b/core/java/android/net/LinkSocket.java
deleted file mode 100644
index 5aa6451..0000000
--- a/core/java/android/net/LinkSocket.java
+++ /dev/null
@@ -1,276 +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.net;
-
-import android.net.LinkCapabilities;
-import android.net.LinkProperties;
-import android.net.LinkSocketNotifier;
-
-import android.util.Log;
-
-import java.io.IOException;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.net.SocketTimeoutException;
-import java.net.UnknownHostException;
-import java.util.HashSet;
-import java.util.Set;
-
-/** @hide */
-public class LinkSocket extends Socket {
- private final static String TAG = "LinkSocket";
- private final static boolean DBG = true;
-
- /**
- * Default constructor
- */
- public LinkSocket() {
- if (DBG) log("LinkSocket() EX");
- }
-
- /**
- * Creates a new unconnected socket.
- * @param notifier a reference to a class that implements {@code LinkSocketNotifier}
- */
- public LinkSocket(LinkSocketNotifier notifier) {
- if (DBG) log("LinkSocket(notifier) EX");
- }
-
- /**
- * Creates a new unconnected socket usign the given proxy type.
- * @param notifier a reference to a class that implements {@code LinkSocketNotifier}
- * @param proxy the specified proxy for this socket
- * @throws IllegalArgumentException if the argument proxy is null or of an invalid type.
- * @throws SecurityException if a security manager exists and it denies the permission
- * to connect to the given proxy.
- */
- public LinkSocket(LinkSocketNotifier notifier, Proxy proxy) {
- if (DBG) log("LinkSocket(notifier, proxy) EX");
- }
-
- /**
- * @return the {@code LinkProperties} for the socket
- */
- public LinkProperties getLinkProperties() {
- if (DBG) log("LinkProperties() EX");
- return new LinkProperties();
- }
-
- /**
- * Set the {@code LinkCapabilies} needed for this socket. If the socket is already connected
- * or is a duplicate socket the request is ignored and {@code false} will
- * be returned. A needs map can be created via the {@code createNeedsMap} static
- * method.
- * @param needs the needs of the socket
- * @return {@code true} if needs are successfully set, {@code false} otherwise
- */
- public boolean setNeededCapabilities(LinkCapabilities needs) {
- if (DBG) log("setNeeds() EX");
- return false;
- }
-
- /**
- * @return the LinkCapabilites set by setNeededCapabilities, empty if none has been set
- */
- public LinkCapabilities getNeededCapabilities() {
- if (DBG) log("getNeeds() EX");
- return null;
- }
-
- /**
- * @return all of the {@code LinkCapabilities} of the link used by this socket
- */
- public LinkCapabilities getCapabilities() {
- if (DBG) log("getCapabilities() EX");
- return null;
- }
-
- /**
- * Returns this LinkSockets set of capabilities, filtered according to
- * the given {@code Set}. Capabilities in the Set but not available from
- * the link will not be reported in the results. Capabilities of the link
- * but not listed in the Set will also not be reported in the results.
- * @param capabilities {@code Set} of capabilities requested
- * @return the filtered {@code LinkCapabilities} of this LinkSocket, may be empty
- */
- public LinkCapabilities getCapabilities(Set<Integer> capabilities) {
- if (DBG) log("getCapabilities(capabilities) EX");
- return new LinkCapabilities();
- }
-
- /**
- * Provide the set of capabilities the application is interested in tracking
- * for this LinkSocket.
- * @param capabilities a {@code Set} of capabilities to track
- */
- public void setTrackedCapabilities(Set<Integer> capabilities) {
- if (DBG) log("setTrackedCapabilities(capabilities) EX");
- }
-
- /**
- * @return the {@code LinkCapabilities} that are tracked, empty if none has been set.
- */
- public Set<Integer> getTrackedCapabilities() {
- if (DBG) log("getTrackedCapabilities(capabilities) EX");
- return new HashSet<Integer>();
- }
-
- /**
- * Connects this socket to the given remote host address and port specified
- * by dstName and dstPort.
- * @param dstName the address of the remote host to connect to
- * @param dstPort the port to connect to on the remote host
- * @param timeout the timeout value in milliseconds or 0 for infinite timeout
- * @throws UnknownHostException if the given dstName is invalid
- * @throws IOException if the socket is already connected or an error occurs
- * while connecting
- * @throws SocketTimeoutException if the timeout fires
- */
- public void connect(String dstName, int dstPort, int timeout)
- throws UnknownHostException, IOException, SocketTimeoutException {
- if (DBG) log("connect(dstName, dstPort, timeout) EX");
- }
-
- /**
- * Connects this socket to the given remote host address and port specified
- * by dstName and dstPort.
- * @param dstName the address of the remote host to connect to
- * @param dstPort the port to connect to on the remote host
- * @throws UnknownHostException if the given dstName is invalid
- * @throws IOException if the socket is already connected or an error occurs
- * while connecting
- */
- public void connect(String dstName, int dstPort)
- throws UnknownHostException, IOException {
- if (DBG) log("connect(dstName, dstPort, timeout) EX");
- }
-
- /**
- * Connects this socket to the given remote host address and port specified
- * by the SocketAddress with the specified timeout.
- * @deprecated Use {@code connect(String dstName, int dstPort, int timeout)}
- * instead. Using this method may result in reduced functionality.
- * @param remoteAddr the address and port of the remote host to connect to
- * @throws IllegalArgumentException if the given SocketAddress is invalid
- * @throws IOException if the socket is already connected or an error occurs
- * while connecting
- * @throws SocketTimeoutException if the timeout expires
- */
- @Override
- @Deprecated
- public void connect(SocketAddress remoteAddr, int timeout)
- throws IOException, SocketTimeoutException {
- if (DBG) log("connect(remoteAddr, timeout) EX DEPRECATED");
- }
-
- /**
- * Connects this socket to the given remote host address and port specified
- * by the SocketAddress.
- * TODO add comment on all these that the network selection happens during connect
- * and may take 30 seconds
- * @deprecated Use {@code connect(String dstName, int dstPort)}
- * Using this method may result in reduced functionality.
- * @param remoteAddr the address and port of the remote host to connect to.
- * @throws IllegalArgumentException if the SocketAddress is invalid or not supported.
- * @throws IOException if the socket is already connected or an error occurs
- * while connecting
- */
- @Override
- @Deprecated
- public void connect(SocketAddress remoteAddr) throws IOException {
- if (DBG) log("connect(remoteAddr) EX DEPRECATED");
- }
-
- /**
- * Connect a duplicate socket socket to the same remote host address and port
- * as the original with a timeout parameter.
- * @param timeout the timeout value in milliseconds or 0 for infinite timeout
- * @throws IOException if the socket is already connected or an error occurs
- * while connecting
- */
- public void connect(int timeout) throws IOException {
- if (DBG) log("connect(timeout) EX");
- }
-
- /**
- * Connect a duplicate socket socket to the same remote host address and port
- * as the original.
- * @throws IOException if the socket is already connected or an error occurs
- * while connecting
- */
- public void connect() throws IOException {
- if (DBG) log("connect() EX");
- }
-
- /**
- * Closes the socket. It is not possible to reconnect or rebind to this
- * socket thereafter which means a new socket instance has to be created.
- * @throws IOException if an error occurs while closing the socket
- */
- @Override
- public synchronized void close() throws IOException {
- if (DBG) log("close() EX");
- }
-
- /**
- * Request that a new LinkSocket be created using a different radio
- * (such as WiFi or 3G) than the current LinkSocket. If a different
- * radio is available a call back will be made via {@code onBetterLinkAvail}.
- * If unable to find a better radio, application will be notified via
- * {@code onNewLinkUnavailable}
- * @see LinkSocketNotifier#onBetterLinkAvailable(LinkSocket, LinkSocket)
- * @param linkRequestReason reason for requesting a new link.
- */
- public void requestNewLink(LinkRequestReason linkRequestReason) {
- if (DBG) log("requestNewLink(linkRequestReason) EX");
- }
-
- /**
- * @deprecated LinkSocket will automatically pick the optimum interface
- * to bind to
- * @param localAddr the specific address and port on the local machine
- * to bind to
- * @throws IOException always as this method is deprecated for LinkSocket
- */
- @Override
- @Deprecated
- public void bind(SocketAddress localAddr) throws UnsupportedOperationException {
- if (DBG) log("bind(localAddr) EX throws IOException");
- throw new UnsupportedOperationException("bind is deprecated for LinkSocket");
- }
-
- /**
- * Reason codes an application can specify when requesting for a new link.
- * TODO: need better documentation
- */
- public static final class LinkRequestReason {
- /** No constructor */
- private LinkRequestReason() {}
-
- /** This link is working properly */
- public static final int LINK_PROBLEM_NONE = 0;
- /** This link has an unknown issue */
- public static final int LINK_PROBLEM_UNKNOWN = 1;
- }
-
- /**
- * Debug logging
- */
- protected static void log(String s) {
- Log.d(TAG, s);
- }
-}
diff --git a/core/java/android/net/LinkSocketNotifier.java b/core/java/android/net/LinkSocketNotifier.java
deleted file mode 100644
index e2429d8..0000000
--- a/core/java/android/net/LinkSocketNotifier.java
+++ /dev/null
@@ -1,86 +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.net;
-
-/**
- * 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.
- * @hide
- */
-public interface LinkSocketNotifier {
- /**
- * This callback function will be called if a better link
- * becomes available.
- * TODO - this shouldn't be checked for all cases - what's the conditional
- * flag?
- * If the duplicate socket is accepted, the original will be marked invalid
- * and additional use will throw exceptions.
- * @param original the original LinkSocket
- * @param duplicate the new LinkSocket that better meets the application
- * requirements
- * @return {@code true} if the application intends to use this link
- *
- * REM
- * TODO - how agressive should we be?
- * At a minimum CS tracks which LS have this turned on and tracks the requirements
- * When a new link becomes available, automatically check if any of the LinkSockets
- * will care.
- * If found, grab a refcount on the link so it doesn't go away and send notification
- * Optionally, periodically setup connection on available networks to check for better links
- * Maybe pass this info into the LinkFactories so condition changes can be acted on more quickly
- */
- public boolean onBetterLinkAvailable(LinkSocket original, LinkSocket duplicate);
-
- /**
- * This callback function will be called when a LinkSocket no longer has
- * an active link.
- * @param socket the LinkSocket that lost its link
- *
- * REM
- * NetworkStateTracker tells us it is disconnected
- * CS checks the table for LS on that link
- * CS calls each callback (need to work out p2p cross process callback)
- */
- public void onLinkLost(LinkSocket socket);
-
- /**
- * This callback function will be called when an application calls
- * requestNewLink on a LinkSocket but the LinkSocket is unable to find
- * a suitable new link.
- * @param socket the LinkSocket for which a new link was not found
- * TODO - why the diff between initial request (sync) and requestNewLink?
- *
- * REM
- * CS process of trying to find a new link must track the LS that started it
- * on failure, call callback
- */
- public void onNewLinkUnavailable(LinkSocket socket);
-
- /**
- * This callback function will be called when any of the notification-marked
- * capabilities of the LinkSocket (e.g. upstream bandwidth) have changed.
- * @param socket the linkSocet for which capabilities have changed
- * @param changedCapabilities the set of capabilities that the application
- * is interested in and have changed (with new values)
- *
- * REM
- * Maybe pass the interesting capabilities into the Links.
- * Get notified of every capability change
- * check for LinkSockets on that Link that are interested in that Capability - call them
- */
- public void onCapabilitiesChanged(LinkSocket socket, LinkCapabilities changedCapabilities);
-}
diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java
index 643e8c2..fa9f479 100644
--- a/core/java/android/net/LocalSocketImpl.java
+++ b/core/java/android/net/LocalSocketImpl.java
@@ -56,7 +56,10 @@ class LocalSocketImpl
/** {@inheritDoc} */
@Override
public int available() throws IOException {
- return available_native(fd);
+ FileDescriptor myFd = fd;
+ if (myFd == null) throw new IOException("socket closed");
+
+ return available_native(myFd);
}
/** {@inheritDoc} */
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index 30b61c5..535bbe2 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -66,7 +66,6 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker {
private Handler mTarget;
private Context mContext;
private LinkProperties mLinkProperties;
- private LinkCapabilities mLinkCapabilities;
private boolean mPrivateDnsRouteSet = false;
private boolean mDefaultRouteSet = false;
@@ -200,11 +199,11 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker {
}
mLinkProperties.setMtu(mContext.getResources().getInteger(
com.android.internal.R.integer.config_mobile_mtu));
- mLinkCapabilities = intent.getParcelableExtra(
- PhoneConstants.DATA_LINK_CAPABILITIES_KEY);
- if (mLinkCapabilities == null) {
- loge("CONNECTED event did not supply link capabilities.");
- mLinkCapabilities = new LinkCapabilities();
+ mNetworkCapabilities = intent.getParcelableExtra(
+ PhoneConstants.DATA_NETWORK_CAPABILITIES_KEY);
+ if (mNetworkCapabilities == null) {
+ loge("CONNECTED event did not supply network capabilities.");
+ mNetworkCapabilities = new NetworkCapabilities();
}
}
@@ -316,10 +315,10 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker {
Slog.d(TAG, "LinkProperties = " );
}
- if (mLinkCapabilities != null) {
- Slog.d(TAG, "LinkCapabilities = " + mLinkCapabilities);
+ if (mNetworkCapabilities != null) {
+ Slog.d(TAG, mNetworkCapabilities.toString());
} else {
- Slog.d(TAG, "LinkCapabilities = " );
+ Slog.d(TAG, "NetworkCapabilities = " );
}
}
@@ -750,14 +749,6 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker {
return new LinkProperties(mLinkProperties);
}
- /**
- * @see android.net.NetworkStateTracker#getLinkCapabilities()
- */
- @Override
- public LinkCapabilities getLinkCapabilities() {
- return new LinkCapabilities(mLinkCapabilities);
- }
-
public void supplyMessenger(Messenger messenger) {
if (VDBG) log(mApnType + " got supplyMessenger");
AsyncChannel ac = new AsyncChannel();
diff --git a/core/java/android/net/LinkCapabilities.aidl b/core/java/android/net/Network.aidl
index df72599..73ba1af 100644
--- a/core/java/android/net/LinkCapabilities.aidl
+++ b/core/java/android/net/Network.aidl
@@ -1,6 +1,6 @@
/*
**
-** Copyright (C) 2010 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.
@@ -17,5 +17,4 @@
package android.net;
-parcelable LinkCapabilities;
-
+parcelable Network;
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
new file mode 100644
index 0000000..64516e6
--- /dev/null
+++ b/core/java/android/net/Network.java
@@ -0,0 +1,250 @@
+/*
+ * 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.NetworkUtils;
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import javax.net.SocketFactory;
+
+/**
+ * Identifies a {@code Network}. This is supplied to applications via
+ * {@link ConnectivityManager.NetworkCallbackListener} in response to
+ * {@link ConnectivityManager#requestNetwork} or {@link ConnectivityManager#listenForNetwork}.
+ * It is used to direct traffic to the given {@code Network}, either on a {@link Socket} basis
+ * through a targeted {@link SocketFactory} or process-wide via {@link #bindProcess}.
+ */
+public class Network implements Parcelable {
+
+ /**
+ * @hide
+ */
+ public final int netId;
+
+ private NetworkBoundSocketFactory mNetworkBoundSocketFactory = null;
+
+ /**
+ * @hide
+ */
+ public Network(int netId) {
+ this.netId = netId;
+ }
+
+ /**
+ * @hide
+ */
+ public Network(Network that) {
+ this.netId = that.netId;
+ }
+
+ /**
+ * Operates the same as {@code InetAddress.getAllByName} except that host
+ * resolution is done on this network.
+ *
+ * @param host the hostname or literal IP string to be resolved.
+ * @return the array of addresses associated with the specified host.
+ * @throws UnknownHostException if the address lookup fails.
+ */
+ public InetAddress[] getAllByName(String host) throws UnknownHostException {
+ return InetAddress.getAllByNameOnNet(host, netId);
+ }
+
+ /**
+ * Operates the same as {@code InetAddress.getByName} except that host
+ * resolution is done on this network.
+ *
+ * @param host
+ * the hostName to be resolved to an address or {@code null}.
+ * @return the {@code InetAddress} instance representing the host.
+ * @throws UnknownHostException
+ * if the address lookup fails.
+ */
+ public InetAddress getByName(String host) throws UnknownHostException {
+ return InetAddress.getByNameOnNet(host, netId);
+ }
+
+ /**
+ * A {@code SocketFactory} that produces {@code Socket}'s bound to this network.
+ */
+ private class NetworkBoundSocketFactory extends SocketFactory {
+ private final int mNetId;
+
+ public NetworkBoundSocketFactory(int netId) {
+ super();
+ mNetId = netId;
+ }
+
+ private void connectToHost(Socket socket, String host, int port) throws IOException {
+ // Lookup addresses only on this Network.
+ InetAddress[] hostAddresses = getAllByName(host);
+ // Try all but last address ignoring exceptions.
+ for (int i = 0; i < hostAddresses.length - 1; i++) {
+ try {
+ socket.connect(new InetSocketAddress(hostAddresses[i], port));
+ return;
+ } catch (IOException e) {
+ }
+ }
+ // Try last address. Do throw exceptions.
+ socket.connect(new InetSocketAddress(hostAddresses[hostAddresses.length - 1], port));
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
+ Socket socket = createSocket();
+ socket.bind(new InetSocketAddress(localHost, localPort));
+ connectToHost(socket, host, port);
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
+ int localPort) throws IOException {
+ Socket socket = createSocket();
+ socket.bind(new InetSocketAddress(localAddress, localPort));
+ socket.connect(new InetSocketAddress(address, port));
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ Socket socket = createSocket();
+ socket.connect(new InetSocketAddress(host, port));
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ Socket socket = createSocket();
+ connectToHost(socket, host, port);
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ Socket socket = new Socket();
+ // Query a property of the underlying socket to ensure the underlying
+ // socket exists so a file descriptor is available to bind to a network.
+ socket.getReuseAddress();
+ NetworkUtils.bindSocketToNetwork(socket.getFileDescriptor$().getInt$(), mNetId);
+ return socket;
+ }
+ }
+
+ /**
+ * Returns a {@link SocketFactory} bound to this network. Any {@link Socket} created by
+ * this factory will have its traffic sent over this {@code Network}. Note that if this
+ * {@code Network} ever disconnects, this factory and any {@link Socket} it produced in the
+ * past or future will cease to work.
+ *
+ * @return a {@link SocketFactory} which produces {@link Socket} instances bound to this
+ * {@code Network}.
+ */
+ public SocketFactory socketFactory() {
+ if (mNetworkBoundSocketFactory == null) {
+ mNetworkBoundSocketFactory = new NetworkBoundSocketFactory(netId);
+ }
+ return mNetworkBoundSocketFactory;
+ }
+
+ /**
+ * Binds the current process to this network. All sockets created in the future (and not
+ * explicitly bound via a bound {@link SocketFactory} (see {@link Network#socketFactory})
+ * will be bound to this network. Note that if this {@code Network} ever disconnects
+ * all sockets created in this way will cease to work. This is by design so an application
+ * doesn't accidentally use sockets it thinks are still bound to a particular {@code Network}.
+ */
+ public void bindProcess() {
+ NetworkUtils.bindProcessToNetwork(netId);
+ }
+
+ /**
+ * Binds host resolutions performed by this process to this network. {@link #bindProcess}
+ * takes precedence over this setting.
+ *
+ * @hide
+ * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
+ */
+ public void bindProcessForHostResolution() {
+ NetworkUtils.bindProcessToNetworkForHostResolution(netId);
+ }
+
+ /**
+ * Clears any process specific {@link Network} binding for host resolution. This does
+ * not clear bindings enacted via {@link #bindProcess}.
+ *
+ * @hide
+ * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
+ */
+ public void unbindProcessForHostResolution() {
+ NetworkUtils.unbindProcessToNetworkForHostResolution();
+ }
+
+ /**
+ * A static utility method to return any {@code Network} currently bound by this process.
+ *
+ * @return {@code Network} to which this process is bound.
+ */
+ public static Network getProcessBoundNetwork() {
+ return new Network(NetworkUtils.getNetworkBoundToProcess());
+ }
+
+ /**
+ * Clear any process specific {@code Network} binding. This reverts a call to
+ * {@link Network#bindProcess}.
+ */
+ public static void unbindProcess() {
+ NetworkUtils.unbindProcessToNetwork();
+ }
+
+ // implement the Parcelable interface
+ public int describeContents() {
+ return 0;
+ }
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(netId);
+ }
+
+ public static final Creator<Network> CREATOR =
+ new Creator<Network>() {
+ public Network createFromParcel(Parcel in) {
+ int netId = in.readInt();
+
+ return new Network(netId);
+ }
+
+ public Network[] newArray(int size) {
+ return new Network[size];
+ }
+ };
+
+ public boolean equals(Object obj) {
+ if (obj instanceof Network == false) return false;
+ Network other = (Network)obj;
+ return this.netId == other.netId;
+ }
+
+ public int hashCode() {
+ return netId * 11;
+ }
+}
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
new file mode 100644
index 0000000..7e8b1f1
--- /dev/null
+++ b/core/java/android/net/NetworkAgent.java
@@ -0,0 +1,202 @@
+/*
+ * 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.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A Utility class for handling for communicating between bearer-specific
+ * code and ConnectivityService.
+ *
+ * A bearer may have more than one NetworkAgent if it can simultaneously
+ * support separate networks (IMS / Internet / MMS Apns on cellular, or
+ * perhaps connections with different SSID or P2P for Wi-Fi).
+ *
+ * @hide
+ */
+public abstract class NetworkAgent extends Handler {
+ private volatile AsyncChannel mAsyncChannel;
+ private final String LOG_TAG;
+ private static final boolean DBG = true;
+ private static final boolean VDBG = true;
+ private final Context mContext;
+ private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>();
+
+ private static final int BASE = Protocol.BASE_NETWORK_AGENT;
+
+ /**
+ * Sent by ConnectivityService to the NetworkAgent to inform it of
+ * suspected connectivity problems on its network. The NetworkAgent
+ * should take steps to verify and correct connectivity.
+ */
+ public static final int CMD_SUSPECT_BAD = BASE;
+
+ /**
+ * Sent by the NetworkAgent (note the EVENT vs CMD prefix) to
+ * ConnectivityService to pass the current NetworkInfo (connection state).
+ * Sent when the NetworkInfo changes, mainly due to change of state.
+ * obj = NetworkInfo
+ */
+ public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1;
+
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to pass the current
+ * NetworkCapabilties.
+ * obj = NetworkCapabilities
+ */
+ public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2;
+
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to pass the current
+ * NetworkProperties.
+ * obj = NetworkProperties
+ */
+ public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3;
+
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to pass the current
+ * network score.
+ * obj = network score Integer
+ */
+ public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4;
+
+ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
+ NetworkCapabilities nc, LinkProperties lp, int score) {
+ super(looper);
+ LOG_TAG = logTag;
+ mContext = context;
+ if (ni == null || nc == null || lp == null) {
+ throw new IllegalArgumentException();
+ }
+
+ if (DBG) log("Registering NetworkAgent");
+ ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
+ new LinkProperties(lp), new NetworkCapabilities(nc), score);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
+ if (mAsyncChannel != null) {
+ log("Received new connection while already connected!");
+ } else {
+ if (DBG) log("NetworkAgent fully connected");
+ AsyncChannel ac = new AsyncChannel();
+ ac.connected(null, this, msg.replyTo);
+ ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+ AsyncChannel.STATUS_SUCCESSFUL);
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = ac;
+ for (Message m : mPreConnectedQueue) {
+ ac.sendMessage(m);
+ }
+ mPreConnectedQueue.clear();
+ }
+ }
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
+ if (DBG) log("CMD_CHANNEL_DISCONNECT");
+ if (mAsyncChannel != null) mAsyncChannel.disconnect();
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+ if (DBG) log("NetworkAgent channel lost");
+ // let the client know CS is done with us.
+ unwanted();
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = null;
+ }
+ break;
+ }
+ case CMD_SUSPECT_BAD: {
+ log("Unhandled Message " + msg);
+ break;
+ }
+ }
+ }
+
+ private void queueOrSendMessage(int what, Object obj) {
+ synchronized (mPreConnectedQueue) {
+ if (mAsyncChannel != null) {
+ mAsyncChannel.sendMessage(what, obj);
+ } else {
+ Message msg = Message.obtain();
+ msg.what = what;
+ msg.obj = obj;
+ mPreConnectedQueue.add(msg);
+ }
+ }
+ }
+
+ /**
+ * Called by the bearer code when it has new LinkProperties data.
+ */
+ public void sendLinkProperties(LinkProperties linkProperties) {
+ queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties));
+ }
+
+ /**
+ * Called by the bearer code when it has new NetworkInfo data.
+ */
+ public void sendNetworkInfo(NetworkInfo networkInfo) {
+ queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, new NetworkInfo(networkInfo));
+ }
+
+ /**
+ * Called by the bearer code when it has new NetworkCapabilities data.
+ */
+ public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) {
+ queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED,
+ new NetworkCapabilities(networkCapabilities));
+ }
+
+ /**
+ * Called by the bearer code when it has a new score for this network.
+ */
+ public void sendNetworkScore(int score) {
+ queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new Integer(score));
+ }
+
+ /**
+ * Called when ConnectivityService has indicated they no longer want this network.
+ * The parent factory should (previously) have received indication of the change
+ * as well, either canceling NetworkRequests or altering their score such that this
+ * network won't be immediately requested again.
+ */
+ abstract protected void unwanted();
+
+ protected void log(String s) {
+ Log.d(LOG_TAG, "NetworkAgent: " + s);
+ }
+}
diff --git a/core/java/android/net/NetworkCapabilities.aidl b/core/java/android/net/NetworkCapabilities.aidl
new file mode 100644
index 0000000..cd7d71c
--- /dev/null
+++ b/core/java/android/net/NetworkCapabilities.aidl
@@ -0,0 +1,21 @@
+/*
+**
+** 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 NetworkCapabilities;
+
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
new file mode 100644
index 0000000..35274f1
--- /dev/null
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -0,0 +1,510 @@
+/*
+ * 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 android.util.Log;
+
+import java.lang.IllegalArgumentException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * This class represents the capabilities of a network. This is used both to specify
+ * needs to {@link ConnectivityManager} and when inspecting a network.
+ *
+ * Note that this replaces the old {@link ConnectivityManager#TYPE_MOBILE} method
+ * of network selection. Rather than indicate a need for Wi-Fi because an application
+ * needs high bandwidth and risk obselence when a new, fast network appears (like LTE),
+ * the application should specify it needs high bandwidth. Similarly if an application
+ * needs an unmetered network for a bulk transfer it can specify that rather than assuming
+ * all cellular based connections are metered and all Wi-Fi based connections are not.
+ */
+public final class NetworkCapabilities implements Parcelable {
+ private static final String TAG = "NetworkCapabilities";
+ private static final boolean DBG = false;
+
+ public NetworkCapabilities() {
+ }
+
+ public NetworkCapabilities(NetworkCapabilities nc) {
+ if (nc != null) {
+ mNetworkCapabilities = nc.mNetworkCapabilities;
+ mTransportTypes = nc.mTransportTypes;
+ mLinkUpBandwidthKbps = nc.mLinkUpBandwidthKbps;
+ mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps;
+ }
+ }
+
+ /**
+ * Represents the network's capabilities. If any are specified they will be satisfied
+ * by any Network that matches all of them.
+ */
+ private long mNetworkCapabilities = (1 << NET_CAPABILITY_NOT_RESTRICTED);
+
+ /**
+ * Indicates this is a network that has the ability to reach the
+ * carrier's MMSC for sending and receiving MMS messages.
+ */
+ public static final int NET_CAPABILITY_MMS = 0;
+
+ /**
+ * Indicates this is a network that has the ability to reach the carrier's
+ * SUPL server, used to retrieve GPS information.
+ */
+ public static final int NET_CAPABILITY_SUPL = 1;
+
+ /**
+ * Indicates this is a network that has the ability to reach the carrier's
+ * DUN or tethering gateway.
+ */
+ public static final int NET_CAPABILITY_DUN = 2;
+
+ /**
+ * Indicates this is a network that has the ability to reach the carrier's
+ * FOTA portal, used for over the air updates.
+ */
+ public static final int NET_CAPABILITY_FOTA = 3;
+
+ /**
+ * Indicates this is a network that has the ability to reach the carrier's
+ * IMS servers, used for network registration and signaling.
+ */
+ public static final int NET_CAPABILITY_IMS = 4;
+
+ /**
+ * Indicates this is a network that has the ability to reach the carrier's
+ * CBS servers, used for carrier specific services.
+ */
+ public static final int NET_CAPABILITY_CBS = 5;
+
+ /**
+ * Indicates this is a network that has the ability to reach a Wi-Fi direct
+ * peer.
+ */
+ public static final int NET_CAPABILITY_WIFI_P2P = 6;
+
+ /**
+ * Indicates this is a network that has the ability to reach a carrier's
+ * Initial Attach servers.
+ */
+ public static final int NET_CAPABILITY_IA = 7;
+
+ /**
+ * Indicates this is a network that has the ability to reach a carrier's
+ * RCS servers, used for Rich Communication Services.
+ */
+ public static final int NET_CAPABILITY_RCS = 8;
+
+ /**
+ * Indicates this is a network that has the ability to reach a carrier's
+ * XCAP servers, used for configuration and control.
+ */
+ public static final int NET_CAPABILITY_XCAP = 9;
+
+ /**
+ * Indicates this is a network that has the ability to reach a carrier's
+ * Emergency IMS servers, used for network signaling during emergency calls.
+ */
+ public static final int NET_CAPABILITY_EIMS = 10;
+
+ /**
+ * Indicates that this network is unmetered.
+ */
+ public static final int NET_CAPABILITY_NOT_METERED = 11;
+
+ /**
+ * Indicates that this network should be able to reach the internet.
+ */
+ public static final int NET_CAPABILITY_INTERNET = 12;
+
+ /**
+ * Indicates that this network is available for general use. If this is not set
+ * applications should not attempt to communicate on this network. Note that this
+ * is simply informative and not enforcement - enforcement is handled via other means.
+ * Set by default.
+ */
+ public static final int NET_CAPABILITY_NOT_RESTRICTED = 13;
+
+ private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_RESTRICTED;
+
+ /**
+ * Adds the given capability to this {@code NetworkCapability} instance.
+ * Multiple capabilities may be applied sequentially. Note that when searching
+ * for a network to satisfy a request, all capabilities requested must be satisfied.
+ *
+ * @param networkCapability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be added.
+ */
+ public void addNetworkCapability(int networkCapability) {
+ if (networkCapability < MIN_NET_CAPABILITY ||
+ networkCapability > MAX_NET_CAPABILITY) {
+ throw new IllegalArgumentException("NetworkCapability out of range");
+ }
+ mNetworkCapabilities |= 1 << networkCapability;
+ }
+
+ /**
+ * Removes (if found) the given capability from this {@code NetworkCapability} instance.
+ *
+ * @param networkCapability the {@code NetworkCapabilities.NET_CAPABILTIY_*} to be removed.
+ */
+ public void removeNetworkCapability(int networkCapability) {
+ if (networkCapability < MIN_NET_CAPABILITY ||
+ networkCapability > MAX_NET_CAPABILITY) {
+ throw new IllegalArgumentException("NetworkCapability out of range");
+ }
+ mNetworkCapabilities &= ~(1 << networkCapability);
+ }
+
+ /**
+ * Gets all the capabilities set on this {@code NetworkCapability} instance.
+ *
+ * @return a {@link Collection} of {@code NetworkCapabilities.NET_CAPABILITY_*} values
+ * for this instance.
+ */
+ public Collection<Integer> getNetworkCapabilities() {
+ return enumerateBits(mNetworkCapabilities);
+ }
+
+ /**
+ * Tests for the presence of a capabilitity on this instance.
+ *
+ * @param networkCapability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be tested for.
+ * @return {@code true} if set on this instance.
+ */
+ public boolean hasCapability(int networkCapability) {
+ if (networkCapability < MIN_NET_CAPABILITY ||
+ networkCapability > MAX_NET_CAPABILITY) {
+ return false;
+ }
+ return ((mNetworkCapabilities & (1 << networkCapability)) != 0);
+ }
+
+ private Collection<Integer> enumerateBits(long val) {
+ ArrayList<Integer> result = new ArrayList<Integer>();
+ int resource = 0;
+ while (val > 0) {
+ if ((val & 1) == 1) result.add(resource);
+ val = val >> 1;
+ resource++;
+ }
+ return result;
+ }
+
+ private void combineNetCapabilities(NetworkCapabilities nc) {
+ this.mNetworkCapabilities |= nc.mNetworkCapabilities;
+ }
+
+ private boolean satisfiedByNetCapabilities(NetworkCapabilities nc) {
+ return ((nc.mNetworkCapabilities & this.mNetworkCapabilities) == this.mNetworkCapabilities);
+ }
+
+ private boolean equalsNetCapabilities(NetworkCapabilities nc) {
+ return (nc.mNetworkCapabilities == this.mNetworkCapabilities);
+ }
+
+ /**
+ * Representing the transport type. Apps should generally not care about transport. A
+ * request for a fast internet connection could be satisfied by a number of different
+ * transports. If any are specified here it will be satisfied a Network that matches
+ * any of them. If a caller doesn't care about the transport it should not specify any.
+ */
+ private long mTransportTypes;
+
+ /**
+ * Indicates this network uses a Cellular transport.
+ */
+ public static final int TRANSPORT_CELLULAR = 0;
+
+ /**
+ * Indicates this network uses a Wi-Fi transport.
+ */
+ public static final int TRANSPORT_WIFI = 1;
+
+ /**
+ * Indicates this network uses a Bluetooth transport.
+ */
+ public static final int TRANSPORT_BLUETOOTH = 2;
+
+ /**
+ * Indicates this network uses an Ethernet transport.
+ */
+ public static final int TRANSPORT_ETHERNET = 3;
+
+ private static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
+ private static final int MAX_TRANSPORT = TRANSPORT_ETHERNET;
+
+ /**
+ * Adds the given transport type to this {@code NetworkCapability} instance.
+ * Multiple transports may be applied sequentially. Note that when searching
+ * for a network to satisfy a request, any listed in the request will satisfy the request.
+ * For example {@code TRANSPORT_WIFI} and {@code TRANSPORT_ETHERNET} added to a
+ * {@code NetworkCapabilities} would cause either a Wi-Fi network or an Ethernet network
+ * to be selected. This is logically different than
+ * {@code NetworkCapabilities.NET_CAPABILITY_*} listed above.
+ *
+ * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be added.
+ */
+ public void addTransportType(int transportType) {
+ if (transportType < MIN_TRANSPORT || transportType > MAX_TRANSPORT) {
+ throw new IllegalArgumentException("TransportType out of range");
+ }
+ mTransportTypes |= 1 << transportType;
+ }
+
+ /**
+ * Removes (if found) the given transport from this {@code NetworkCapability} instance.
+ *
+ * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be removed.
+ */
+ public void removeTransportType(int transportType) {
+ if (transportType < MIN_TRANSPORT || transportType > MAX_TRANSPORT) {
+ throw new IllegalArgumentException("TransportType out of range");
+ }
+ mTransportTypes &= ~(1 << transportType);
+ }
+
+ /**
+ * Gets all the transports set on this {@code NetworkCapability} instance.
+ *
+ * @return a {@link Collection} of {@code NetworkCapabilities.TRANSPORT_*} values
+ * for this instance.
+ */
+ public Collection<Integer> getTransportTypes() {
+ return enumerateBits(mTransportTypes);
+ }
+
+ /**
+ * Tests for the presence of a transport on this instance.
+ *
+ * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be tested for.
+ * @return {@code true} if set on this instance.
+ */
+ public boolean hasTransport(int transportType) {
+ if (transportType < MIN_TRANSPORT || transportType > MAX_TRANSPORT) {
+ return false;
+ }
+ return ((mTransportTypes & (1 << transportType)) != 0);
+ }
+
+ private void combineTransportTypes(NetworkCapabilities nc) {
+ this.mTransportTypes |= nc.mTransportTypes;
+ }
+ private boolean satisfiedByTransportTypes(NetworkCapabilities nc) {
+ return ((this.mTransportTypes == 0) ||
+ ((this.mTransportTypes & nc.mTransportTypes) != 0));
+ }
+ private boolean equalsTransportTypes(NetworkCapabilities nc) {
+ return (nc.mTransportTypes == this.mTransportTypes);
+ }
+
+ /**
+ * Passive link bandwidth. This is a rough guide of the expected peak bandwidth
+ * for the first hop on the given transport. It is not measured, but may take into account
+ * link parameters (Radio technology, allocated channels, etc).
+ */
+ private int mLinkUpBandwidthKbps;
+ private int mLinkDownBandwidthKbps;
+
+ /**
+ * Sets the upstream bandwidth for this network in Kbps. This always only refers to
+ * the estimated first hop transport bandwidth.
+ * <p>
+ * Note that when used to request a network, this specifies the minimum acceptable.
+ * When received as the state of an existing network this specifies the typical
+ * first hop bandwidth expected. This is never measured, but rather is inferred
+ * from technology type and other link parameters. It could be used to differentiate
+ * between very slow 1xRTT cellular links and other faster networks or even between
+ * 802.11b vs 802.11AC wifi technologies. It should not be used to differentiate between
+ * fast backhauls and slow backhauls.
+ *
+ * @param upKbps the estimated first hop upstream (device to network) bandwidth.
+ */
+ public void setLinkUpstreamBandwidthKbps(int upKbps) {
+ mLinkUpBandwidthKbps = upKbps;
+ }
+
+ /**
+ * Retrieves the upstream bandwidth for this network in Kbps. This always only refers to
+ * the estimated first hop transport bandwidth.
+ *
+ * @return The estimated first hop upstream (device to network) bandwidth.
+ */
+ public int getLinkUpstreamBandwidthKbps() {
+ return mLinkUpBandwidthKbps;
+ }
+
+ /**
+ * Sets the downstream bandwidth for this network in Kbps. This always only refers to
+ * the estimated first hop transport bandwidth.
+ * <p>
+ * Note that when used to request a network, this specifies the minimum acceptable.
+ * When received as the state of an existing network this specifies the typical
+ * first hop bandwidth expected. This is never measured, but rather is inferred
+ * from technology type and other link parameters. It could be used to differentiate
+ * between very slow 1xRTT cellular links and other faster networks or even between
+ * 802.11b vs 802.11AC wifi technologies. It should not be used to differentiate between
+ * fast backhauls and slow backhauls.
+ *
+ * @param downKbps the estimated first hop downstream (network to device) bandwidth.
+ */
+ public void setLinkDownstreamBandwidthKbps(int downKbps) {
+ mLinkDownBandwidthKbps = downKbps;
+ }
+
+ /**
+ * Retrieves the downstream bandwidth for this network in Kbps. This always only refers to
+ * the estimated first hop transport bandwidth.
+ *
+ * @return The estimated first hop downstream (network to device) bandwidth.
+ */
+ public int getLinkDownstreamBandwidthKbps() {
+ return mLinkDownBandwidthKbps;
+ }
+
+ private void combineLinkBandwidths(NetworkCapabilities nc) {
+ this.mLinkUpBandwidthKbps =
+ Math.max(this.mLinkUpBandwidthKbps, nc.mLinkUpBandwidthKbps);
+ this.mLinkDownBandwidthKbps =
+ Math.max(this.mLinkDownBandwidthKbps, nc.mLinkDownBandwidthKbps);
+ }
+ private boolean satisfiedByLinkBandwidths(NetworkCapabilities nc) {
+ return !(this.mLinkUpBandwidthKbps > nc.mLinkUpBandwidthKbps ||
+ this.mLinkDownBandwidthKbps > nc.mLinkDownBandwidthKbps);
+ }
+ private boolean equalsLinkBandwidths(NetworkCapabilities nc) {
+ return (this.mLinkUpBandwidthKbps == nc.mLinkUpBandwidthKbps &&
+ this.mLinkDownBandwidthKbps == nc.mLinkDownBandwidthKbps);
+ }
+
+ /**
+ * Combine a set of Capabilities to this one. Useful for coming up with the complete set
+ * {@hide}
+ */
+ public void combineCapabilities(NetworkCapabilities nc) {
+ combineNetCapabilities(nc);
+ combineTransportTypes(nc);
+ combineLinkBandwidths(nc);
+ }
+
+ /**
+ * Check if our requirements are satisfied by the given Capabilities.
+ * {@hide}
+ */
+ public boolean satisfiedByNetworkCapabilities(NetworkCapabilities nc) {
+ return (nc != null &&
+ satisfiedByNetCapabilities(nc) &&
+ satisfiedByTransportTypes(nc) &&
+ satisfiedByLinkBandwidths(nc));
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || (obj instanceof NetworkCapabilities == false)) return false;
+ NetworkCapabilities that = (NetworkCapabilities)obj;
+ return (equalsNetCapabilities(that) &&
+ equalsTransportTypes(that) &&
+ equalsLinkBandwidths(that));
+ }
+
+ @Override
+ public int hashCode() {
+ return ((int)(mNetworkCapabilities & 0xFFFFFFFF) +
+ ((int)(mNetworkCapabilities >> 32) * 3) +
+ ((int)(mTransportTypes & 0xFFFFFFFF) * 5) +
+ ((int)(mTransportTypes >> 32) * 7) +
+ (mLinkUpBandwidthKbps * 11) +
+ (mLinkDownBandwidthKbps * 13));
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mNetworkCapabilities);
+ dest.writeLong(mTransportTypes);
+ dest.writeInt(mLinkUpBandwidthKbps);
+ dest.writeInt(mLinkDownBandwidthKbps);
+ }
+ public static final Creator<NetworkCapabilities> CREATOR =
+ new Creator<NetworkCapabilities>() {
+ public NetworkCapabilities createFromParcel(Parcel in) {
+ NetworkCapabilities netCap = new NetworkCapabilities();
+
+ netCap.mNetworkCapabilities = in.readLong();
+ netCap.mTransportTypes = in.readLong();
+ netCap.mLinkUpBandwidthKbps = in.readInt();
+ netCap.mLinkDownBandwidthKbps = in.readInt();
+ return netCap;
+ }
+ public NetworkCapabilities[] newArray(int size) {
+ return new NetworkCapabilities[size];
+ }
+ };
+
+ public String toString() {
+ Collection<Integer> types = getTransportTypes();
+ String transports = (types.size() > 0 ? " Transports: " : "");
+ Iterator<Integer> i = types.iterator();
+ while (i.hasNext()) {
+ switch (i.next()) {
+ case TRANSPORT_CELLULAR: transports += "CELLULAR"; break;
+ case TRANSPORT_WIFI: transports += "WIFI"; break;
+ case TRANSPORT_BLUETOOTH: transports += "BLUETOOTH"; break;
+ case TRANSPORT_ETHERNET: transports += "ETHERNET"; break;
+ }
+ if (i.hasNext()) transports += "|";
+ }
+
+ types = getNetworkCapabilities();
+ String capabilities = (types.size() > 0 ? " Capabilities: " : "");
+ i = types.iterator();
+ while (i.hasNext()) {
+ switch (i.next().intValue()) {
+ case NET_CAPABILITY_MMS: capabilities += "MMS"; break;
+ case NET_CAPABILITY_SUPL: capabilities += "SUPL"; break;
+ case NET_CAPABILITY_DUN: capabilities += "DUN"; break;
+ case NET_CAPABILITY_FOTA: capabilities += "FOTA"; break;
+ case NET_CAPABILITY_IMS: capabilities += "IMS"; break;
+ case NET_CAPABILITY_CBS: capabilities += "CBS"; break;
+ case NET_CAPABILITY_WIFI_P2P: capabilities += "WIFI_P2P"; break;
+ case NET_CAPABILITY_IA: capabilities += "IA"; break;
+ case NET_CAPABILITY_RCS: capabilities += "RCS"; break;
+ case NET_CAPABILITY_XCAP: capabilities += "XCAP"; break;
+ case NET_CAPABILITY_EIMS: capabilities += "EIMS"; break;
+ case NET_CAPABILITY_NOT_METERED: capabilities += "NOT_METERED"; break;
+ case NET_CAPABILITY_INTERNET: capabilities += "INTERNET"; break;
+ case NET_CAPABILITY_NOT_RESTRICTED: capabilities += "NOT_RESTRICTED"; break;
+ }
+ if (i.hasNext()) capabilities += "&";
+ }
+
+ String upBand = ((mLinkUpBandwidthKbps > 0) ? " LinkUpBandwidth>=" +
+ mLinkUpBandwidthKbps + "Kbps" : "");
+ String dnBand = ((mLinkDownBandwidthKbps > 0) ? " LinkDnBandwidth>=" +
+ mLinkDownBandwidthKbps + "Kbps" : "");
+
+ return "[" + transports + capabilities + upBand + dnBand + "]";
+ }
+}
diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java
new file mode 100644
index 0000000..a20e8e7
--- /dev/null
+++ b/core/java/android/net/NetworkFactory.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.net;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+
+/**
+ * A NetworkFactory is an entity that creates NetworkAgent objects.
+ * The bearers register with ConnectivityService using {@link #register} and
+ * their factory will start receiving scored NetworkRequests. NetworkRequests
+ * can be filtered 3 ways: by NetworkCapabilities, by score and more complexly by
+ * overridden function. All of these can be dynamic - changing NetworkCapabilities
+ * or score forces re-evaluation of all current requests.
+ *
+ * If any requests pass the filter some overrideable functions will be called.
+ * If the bearer only cares about very simple start/stopNetwork callbacks, those
+ * functions can be overridden. If the bearer needs more interaction, it can
+ * override addNetworkRequest and removeNetworkRequest which will give it each
+ * request that passes their current filters.
+ * @hide
+ **/
+public class NetworkFactory extends Handler {
+ private static final boolean DBG = true;
+
+ private static final int BASE = Protocol.BASE_NETWORK_FACTORY;
+ /**
+ * Pass a network request to the bearer. If the bearer believes it can
+ * satisfy the request it should connect to the network and create a
+ * NetworkAgent. Once the NetworkAgent is fully functional it will
+ * register itself with ConnectivityService using registerNetworkAgent.
+ * If the bearer cannot immediately satisfy the request (no network,
+ * user disabled the radio, lower-scored network) it should remember
+ * any NetworkRequests it may be able to satisfy in the future. It may
+ * disregard any that it will never be able to service, for example
+ * those requiring a different bearer.
+ * msg.obj = NetworkRequest
+ * msg.arg1 = score - the score of the any network currently satisfying this
+ * request. If this bearer knows in advance it cannot
+ * exceed this score it should not try to connect, holding the request
+ * for the future.
+ * Note that subsequent events may give a different (lower
+ * or higher) score for this request, transmitted to each
+ * NetworkFactory through additional CMD_REQUEST_NETWORK msgs
+ * with the same NetworkRequest but an updated score.
+ * Also, network conditions may change for this bearer
+ * allowing for a better score in the future.
+ */
+ public static final int CMD_REQUEST_NETWORK = BASE;
+
+ /**
+ * Cancel a network request
+ * msg.obj = NetworkRequest
+ */
+ public static final int CMD_CANCEL_REQUEST = BASE + 1;
+
+ /**
+ * Internally used to set our best-guess score.
+ * msg.arg1 = new score
+ */
+ private static final int CMD_SET_SCORE = BASE + 2;
+
+ /**
+ * Internally used to set our current filter for coarse bandwidth changes with
+ * technology changes.
+ * msg.obj = new filter
+ */
+ private static final int CMD_SET_FILTER = BASE + 3;
+
+ private final Context mContext;
+ private final String LOG_TAG;
+
+ private final SparseArray<NetworkRequestInfo> mNetworkRequests =
+ new SparseArray<NetworkRequestInfo>();
+
+ private int mScore;
+ private NetworkCapabilities mCapabilityFilter;
+
+ private int mRefCount = 0;
+ private Messenger mMessenger = null;
+
+ public NetworkFactory(Looper looper, Context context, String logTag,
+ NetworkCapabilities filter) {
+ super(looper);
+ LOG_TAG = logTag;
+ mContext = context;
+ mCapabilityFilter = filter;
+ }
+
+ public void register() {
+ if (DBG) log("Registering NetworkFactory");
+ if (mMessenger == null) {
+ mMessenger = new Messenger(this);
+ ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger, LOG_TAG);
+ }
+ }
+
+ public void unregister() {
+ if (DBG) log("Unregistering NetworkFactory");
+ if (mMessenger != null) {
+ ConnectivityManager.from(mContext).unregisterNetworkFactory(mMessenger);
+ mMessenger = null;
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_REQUEST_NETWORK: {
+ handleAddRequest((NetworkRequest)msg.obj, msg.arg1);
+ break;
+ }
+ case CMD_CANCEL_REQUEST: {
+ handleRemoveRequest((NetworkRequest) msg.obj);
+ break;
+ }
+ case CMD_SET_SCORE: {
+ handleSetScore(msg.arg1);
+ break;
+ }
+ case CMD_SET_FILTER: {
+ handleSetFilter((NetworkCapabilities) msg.obj);
+ break;
+ }
+ }
+ }
+
+ private class NetworkRequestInfo {
+ public final NetworkRequest request;
+ public int score;
+ public boolean requested; // do we have a request outstanding, limited by score
+
+ public NetworkRequestInfo(NetworkRequest request, int score) {
+ this.request = request;
+ this.score = score;
+ this.requested = false;
+ }
+ }
+
+ private void handleAddRequest(NetworkRequest request, int score) {
+ NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
+ if (n == null) {
+ n = new NetworkRequestInfo(request, score);
+ mNetworkRequests.put(n.request.requestId, n);
+ } else {
+ n.score = score;
+ }
+ if (DBG) log("got request " + request + " with score " + score);
+ if (DBG) log(" my score=" + mScore + ", my filter=" + mCapabilityFilter);
+
+ evalRequest(n);
+ }
+
+ private void handleRemoveRequest(NetworkRequest request) {
+ NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
+ if (n != null && n.requested) {
+ mNetworkRequests.remove(request.requestId);
+ releaseNetworkFor(n.request);
+ }
+ }
+
+ private void handleSetScore(int score) {
+ mScore = score;
+ evalRequests();
+ }
+
+ private void handleSetFilter(NetworkCapabilities netCap) {
+ mCapabilityFilter = netCap;
+ evalRequests();
+ }
+
+ /**
+ * Overridable function to provide complex filtering.
+ * Called for every request every time a new NetworkRequest is seen
+ * and whenever the filterScore or filterNetworkCapabilities change.
+ *
+ * acceptRequest can be overriden to provide complex filter behavior
+ * for the incoming requests
+ *
+ * For output, this class will call {@link #needNetworkFor} and
+ * {@link #releaseNetworkFor} for every request that passes the filters.
+ * If you don't need to see every request, you can leave the base
+ * implementations of those two functions and instead override
+ * {@link #startNetwork} and {@link #stopNetwork}.
+ *
+ * If you want to see every score fluctuation on every request, set
+ * your score filter to a very high number and watch {@link #needNetworkFor}.
+ *
+ * @return {@code true} to accept the request.
+ */
+ public boolean acceptRequest(NetworkRequest request, int score) {
+ return true;
+ }
+
+ private void evalRequest(NetworkRequestInfo n) {
+ if (n.requested == false && n.score < mScore &&
+ n.request.networkCapabilities.satisfiedByNetworkCapabilities(
+ mCapabilityFilter) && acceptRequest(n.request, n.score)) {
+ needNetworkFor(n.request, n.score);
+ n.requested = true;
+ } else if (n.requested == true &&
+ (n.score > mScore || n.request.networkCapabilities.satisfiedByNetworkCapabilities(
+ mCapabilityFilter) == false || acceptRequest(n.request, n.score) == false)) {
+ releaseNetworkFor(n.request);
+ n.requested = false;
+ }
+ }
+
+ private void evalRequests() {
+ for (int i = 0; i < mNetworkRequests.size(); i++) {
+ NetworkRequestInfo n = mNetworkRequests.valueAt(i);
+
+ evalRequest(n);
+ }
+ }
+
+ // override to do simple mode (request independent)
+ protected void startNetwork() { }
+ protected void stopNetwork() { }
+
+ // override to do fancier stuff
+ protected void needNetworkFor(NetworkRequest networkRequest, int score) {
+ if (++mRefCount == 1) startNetwork();
+ }
+
+ protected void releaseNetworkFor(NetworkRequest networkRequest) {
+ if (--mRefCount == 0) stopNetwork();
+ }
+
+
+ public void addNetworkRequest(NetworkRequest networkRequest, int score) {
+ sendMessage(obtainMessage(CMD_REQUEST_NETWORK,
+ new NetworkRequestInfo(networkRequest, score)));
+ }
+
+ public void removeNetworkRequest(NetworkRequest networkRequest) {
+ sendMessage(obtainMessage(CMD_CANCEL_REQUEST, networkRequest));
+ }
+
+ public void setScoreFilter(int score) {
+ sendMessage(obtainMessage(CMD_SET_SCORE, score, 0));
+ }
+
+ public void setCapabilityFilter(NetworkCapabilities netCap) {
+ sendMessage(obtainMessage(CMD_SET_FILTER, new NetworkCapabilities(netCap)));
+ }
+
+ protected void log(String s) {
+ Log.d(LOG_TAG, s);
+ }
+}
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 53b1308..d279412 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -188,6 +188,15 @@ public class NetworkInfo implements Parcelable {
}
/**
+ * @hide
+ */
+ public void setType(int type) {
+ synchronized (this) {
+ mNetworkType = type;
+ }
+ }
+
+ /**
* Return a network-type-specific integer describing the subtype
* of the network.
* @return the network subtype
@@ -198,7 +207,10 @@ public class NetworkInfo implements Parcelable {
}
}
- void setSubtype(int subtype, String subtypeName) {
+ /**
+ * @hide
+ */
+ public void setSubtype(int subtype, String subtypeName) {
synchronized (this) {
mSubtype = subtype;
mSubtypeName = subtypeName;
@@ -420,7 +432,7 @@ public class NetworkInfo implements Parcelable {
@Override
public String toString() {
synchronized (this) {
- StringBuilder builder = new StringBuilder("NetworkInfo: ");
+ StringBuilder builder = new StringBuilder("[");
builder.append("type: ").append(getTypeName()).append("[").append(getSubtypeName()).
append("], state: ").append(mState).append("/").append(mDetailedState).
append(", reason: ").append(mReason == null ? "(unspecified)" : mReason).
@@ -429,7 +441,8 @@ public class NetworkInfo implements Parcelable {
append(", failover: ").append(mIsFailover).
append(", isAvailable: ").append(mIsAvailable).
append(", isConnectedToProvisioningNetwork: ").
- append(mIsConnectedToProvisioningNetwork);
+ append(mIsConnectedToProvisioningNetwork).
+ append("]");
return builder.toString();
}
}
diff --git a/core/java/android/net/NetworkRequest.aidl b/core/java/android/net/NetworkRequest.aidl
new file mode 100644
index 0000000..508defc
--- /dev/null
+++ b/core/java/android/net/NetworkRequest.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.net;
+
+parcelable NetworkRequest;
+
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
new file mode 100644
index 0000000..47377e9
--- /dev/null
+++ b/core/java/android/net/NetworkRequest.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 android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Defines a request for a network, made by calling {@link ConnectivityManager#requestNetwork}
+ * or {@link ConnectivityManager#listenForNetwork}.
+ *
+ * This token records the {@link NetworkCapabilities} used to make the request and identifies
+ * the request. It should be used to release the request via
+ * {@link ConnectivityManager#releaseNetworkRequest} when the network is no longer desired.
+ */
+public class NetworkRequest implements Parcelable {
+ /**
+ * The {@link NetworkCapabilities} that define this request. This should not be modified.
+ * The networkCapabilities of the request are set when
+ * {@link ConnectivityManager#requestNetwork} is called and the value is presented here
+ * as a convenient reminder of what was requested.
+ */
+ public final NetworkCapabilities networkCapabilities;
+
+ /**
+ * Identifies the request. NetworkRequests should only be constructed by
+ * the Framework and given out to applications as tokens to be used to identify
+ * the request.
+ * @hide
+ */
+ public final int requestId;
+
+ /**
+ * Set for legacy requests and the default. Set to TYPE_NONE for none.
+ * Causes CONNECTIVITY_ACTION broadcasts to be sent.
+ * @hide
+ */
+ public final int legacyType;
+
+ /**
+ * @hide
+ */
+ public NetworkRequest(NetworkCapabilities nc, int legacyType, int rId) {
+ requestId = rId;
+ networkCapabilities = nc;
+ this.legacyType = legacyType;
+ }
+
+ /**
+ * @hide
+ */
+ public NetworkRequest(NetworkRequest that) {
+ networkCapabilities = new NetworkCapabilities(that.networkCapabilities);
+ requestId = that.requestId;
+ this.legacyType = that.legacyType;
+ }
+
+ // implement the Parcelable interface
+ public int describeContents() {
+ return 0;
+ }
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(networkCapabilities, flags);
+ dest.writeInt(legacyType);
+ dest.writeInt(requestId);
+ }
+ public static final Creator<NetworkRequest> CREATOR =
+ new Creator<NetworkRequest>() {
+ public NetworkRequest createFromParcel(Parcel in) {
+ NetworkCapabilities nc = (NetworkCapabilities)in.readParcelable(null);
+ int legacyType = in.readInt();
+ int requestId = in.readInt();
+ NetworkRequest result = new NetworkRequest(nc, legacyType, requestId);
+ return result;
+ }
+ public NetworkRequest[] newArray(int size) {
+ return new NetworkRequest[size];
+ }
+ };
+
+ public String toString() {
+ return "NetworkRequest [ id=" + requestId + ", legacyType=" + legacyType +
+ ", " + networkCapabilities.toString() + " ]";
+ }
+
+ public boolean equals(Object obj) {
+ if (obj instanceof NetworkRequest == false) return false;
+ NetworkRequest that = (NetworkRequest)obj;
+ return (that.legacyType == this.legacyType &&
+ that.requestId == this.requestId &&
+ ((that.networkCapabilities == null && this.networkCapabilities == null) ||
+ (that.networkCapabilities != null &&
+ that.networkCapabilities.equals(this.networkCapabilities))));
+ }
+
+ public int hashCode() {
+ return requestId + (legacyType * 1013) +
+ (networkCapabilities.hashCode() * 1051);
+ }
+}
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
index fbe1f82..2e0e9e4 100644
--- a/core/java/android/net/NetworkState.java
+++ b/core/java/android/net/NetworkState.java
@@ -28,21 +28,21 @@ public class NetworkState implements Parcelable {
public final NetworkInfo networkInfo;
public final LinkProperties linkProperties;
- public final LinkCapabilities linkCapabilities;
+ public final NetworkCapabilities networkCapabilities;
/** Currently only used by testing. */
public final String subscriberId;
public final String networkId;
public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
- LinkCapabilities linkCapabilities) {
- this(networkInfo, linkProperties, linkCapabilities, null, null);
+ NetworkCapabilities networkCapabilities) {
+ this(networkInfo, linkProperties, networkCapabilities, null, null);
}
public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
- LinkCapabilities linkCapabilities, String subscriberId, String networkId) {
+ NetworkCapabilities networkCapabilities, String subscriberId, String networkId) {
this.networkInfo = networkInfo;
this.linkProperties = linkProperties;
- this.linkCapabilities = linkCapabilities;
+ this.networkCapabilities = networkCapabilities;
this.subscriberId = subscriberId;
this.networkId = networkId;
}
@@ -50,7 +50,7 @@ public class NetworkState implements Parcelable {
public NetworkState(Parcel in) {
networkInfo = in.readParcelable(null);
linkProperties = in.readParcelable(null);
- linkCapabilities = in.readParcelable(null);
+ networkCapabilities = in.readParcelable(null);
subscriberId = in.readString();
networkId = in.readString();
}
@@ -64,7 +64,7 @@ public class NetworkState implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
out.writeParcelable(networkInfo, flags);
out.writeParcelable(linkProperties, flags);
- out.writeParcelable(linkCapabilities, flags);
+ out.writeParcelable(networkCapabilities, flags);
out.writeString(subscriberId);
out.writeString(networkId);
}
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index c49b1d1..35500cc 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -111,12 +111,9 @@ public interface NetworkStateTracker {
public LinkProperties getLinkProperties();
/**
- * A capability is an Integer/String pair, the capabilities
- * are defined in the class LinkSocket#Key.
- *
* @return a copy of this connections capabilities, may be empty but never null.
*/
- public LinkCapabilities getLinkCapabilities();
+ public NetworkCapabilities getNetworkCapabilities();
/**
* Get interesting information about this network link
@@ -250,4 +247,14 @@ public interface NetworkStateTracker {
*/
public void stopSampling(SamplingDataTracker.SamplingSnapshot s);
+ /*
+ * Record the current netId
+ */
+ public void setNetId(int netId);
+
+ /*
+ * ?
+ */
+ public Network getNetwork();
+
}
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index b24d396..edb3286 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -109,6 +109,50 @@ public class NetworkUtils {
public native static void markSocket(int socketfd, int mark);
/**
+ * Binds the current process to the network designated by {@code netId}. All sockets created
+ * in the future (and not explicitly bound via a bound {@link SocketFactory} (see
+ * {@link Network#socketFactory}) will be bound to this network. Note that if this
+ * {@code Network} ever disconnects all sockets created in this way will cease to work. This
+ * is by design so an application doesn't accidentally use sockets it thinks are still bound to
+ * a particular {@code Network}.
+ */
+ public native static void bindProcessToNetwork(int netId);
+
+ /**
+ * Clear any process specific {@code Network} binding. This reverts a call to
+ * {@link #bindProcessToNetwork}.
+ */
+ public native static void unbindProcessToNetwork();
+
+ /**
+ * Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if
+ * {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}.
+ */
+ public native static int getNetworkBoundToProcess();
+
+ /**
+ * Binds host resolutions performed by this process to the network designated by {@code netId}.
+ * {@link #bindProcessToNetwork} takes precedence over this setting.
+ *
+ * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
+ */
+ public native static void bindProcessToNetworkForHostResolution(int netId);
+
+ /**
+ * Clears any process specific {@link Network} binding for host resolution. This does
+ * not clear bindings enacted via {@link #bindProcessToNetwork}.
+ *
+ * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
+ */
+ public native static void unbindProcessToNetworkForHostResolution();
+
+ /**
+ * Explicitly binds {@code socketfd} to the network designated by {@code netId}. This
+ * overrides any binding via {@link #bindProcessToNetwork}.
+ */
+ public native static void bindSocketToNetwork(int socketfd, int netId);
+
+ /**
* Convert a IPv4 address from an integer to an InetAddress.
* @param hostAddress an int corresponding to the IPv4 address in network byte order
*/
diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java
index 8f41e85..6a78c29 100644
--- a/core/java/android/net/Proxy.java
+++ b/core/java/android/net/Proxy.java
@@ -273,19 +273,19 @@ public final class Proxy {
String host = null;
String port = null;
String exclList = null;
- String pacFileUrl = null;
+ Uri pacFileUrl = Uri.EMPTY;
if (p != null) {
host = p.getHost();
port = Integer.toString(p.getPort());
exclList = p.getExclusionListAsString();
- pacFileUrl = p.getPacFileUrl().toString();
+ pacFileUrl = p.getPacFileUrl();
}
setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
}
/** @hide */
public static final void setHttpProxySystemProperty(String host, String port, String exclList,
- String pacFileUrl) {
+ Uri pacFileUrl) {
if (exclList != null) exclList = exclList.replace(",", "|");
if (false) Log.d(TAG, "setHttpProxySystemProperty :"+host+":"+port+" - "+exclList);
if (host != null) {
@@ -309,7 +309,7 @@ public final class Proxy {
System.clearProperty("http.nonProxyHosts");
System.clearProperty("https.nonProxyHosts");
}
- if (!TextUtils.isEmpty(pacFileUrl)) {
+ if (!Uri.EMPTY.equals(pacFileUrl)) {
ProxySelector.setDefault(new PacProxySelector());
} else {
ProxySelector.setDefault(sDefaultProxySelector);
diff --git a/core/java/android/net/ProxyDataTracker.java b/core/java/android/net/ProxyDataTracker.java
index 461e8b8..4973b3d 100644
--- a/core/java/android/net/ProxyDataTracker.java
+++ b/core/java/android/net/ProxyDataTracker.java
@@ -104,7 +104,7 @@ public class ProxyDataTracker extends BaseNetworkStateTracker {
public ProxyDataTracker() {
mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_PROXY, 0, NETWORK_TYPE, "");
mLinkProperties = new LinkProperties();
- mLinkCapabilities = new LinkCapabilities();
+ mNetworkCapabilities = new NetworkCapabilities();
mNetworkInfo.setIsAvailable(true);
try {
mLinkProperties.addDns(InetAddress.getByName(DNS1));
diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java
index b40941f..7ea6bae 100644
--- a/core/java/android/net/ProxyInfo.java
+++ b/core/java/android/net/ProxyInfo.java
@@ -44,7 +44,7 @@ public class ProxyInfo implements Parcelable {
private String mExclusionList;
private String[] mParsedExclusionList;
- private String mPacFileUrl;
+ private Uri mPacFileUrl;
/**
*@hide
*/
@@ -85,7 +85,7 @@ public class ProxyInfo implements Parcelable {
* at the specified URL.
*/
public static ProxyInfo buildPacProxy(Uri pacUri) {
- return new ProxyInfo(pacUri.toString());
+ return new ProxyInfo(pacUri);
}
/**
@@ -96,27 +96,45 @@ public class ProxyInfo implements Parcelable {
mHost = host;
mPort = port;
setExclusionList(exclList);
+ mPacFileUrl = Uri.EMPTY;
}
/**
* Create a ProxyProperties that points at a PAC URL.
* @hide
*/
- public ProxyInfo(String pacFileUrl) {
+ public ProxyInfo(Uri pacFileUrl) {
mHost = LOCAL_HOST;
mPort = LOCAL_PORT;
setExclusionList(LOCAL_EXCL_LIST);
+ if (pacFileUrl == null) {
+ throw new NullPointerException();
+ }
mPacFileUrl = pacFileUrl;
}
/**
+ * Create a ProxyProperties that points at a PAC URL.
+ * @hide
+ */
+ public ProxyInfo(String pacFileUrl) {
+ mHost = LOCAL_HOST;
+ mPort = LOCAL_PORT;
+ setExclusionList(LOCAL_EXCL_LIST);
+ mPacFileUrl = Uri.parse(pacFileUrl);
+ }
+
+ /**
* Only used in PacManager after Local Proxy is bound.
* @hide
*/
- public ProxyInfo(String pacFileUrl, int localProxyPort) {
+ public ProxyInfo(Uri pacFileUrl, int localProxyPort) {
mHost = LOCAL_HOST;
mPort = localProxyPort;
setExclusionList(LOCAL_EXCL_LIST);
+ if (pacFileUrl == null) {
+ throw new NullPointerException();
+ }
mPacFileUrl = pacFileUrl;
}
@@ -125,7 +143,7 @@ public class ProxyInfo implements Parcelable {
mPort = port;
mExclusionList = exclList;
mParsedExclusionList = parsedExclList;
- mPacFileUrl = null;
+ mPacFileUrl = Uri.EMPTY;
}
// copy constructor instead of clone
@@ -139,6 +157,8 @@ public class ProxyInfo implements Parcelable {
mPacFileUrl = source.mPacFileUrl;
mExclusionList = source.getExclusionListAsString();
mParsedExclusionList = source.mParsedExclusionList;
+ } else {
+ mPacFileUrl = Uri.EMPTY;
}
}
@@ -158,10 +178,7 @@ public class ProxyInfo implements Parcelable {
* no PAC script.
*/
public Uri getPacFileUrl() {
- if (TextUtils.isEmpty(mPacFileUrl)) {
- return null;
- }
- return Uri.parse(mPacFileUrl);
+ return mPacFileUrl;
}
/**
@@ -210,7 +227,7 @@ public class ProxyInfo implements Parcelable {
* @hide
*/
public boolean isValid() {
- if (!TextUtils.isEmpty(mPacFileUrl)) return true;
+ if (!Uri.EMPTY.equals(mPacFileUrl)) return true;
return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost,
mPort == 0 ? "" : Integer.toString(mPort),
mExclusionList == null ? "" : mExclusionList);
@@ -234,7 +251,7 @@ public class ProxyInfo implements Parcelable {
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
- if (mPacFileUrl != null) {
+ if (!Uri.EMPTY.equals(mPacFileUrl)) {
sb.append("PAC Script: ");
sb.append(mPacFileUrl);
} else if (mHost != null) {
@@ -257,13 +274,15 @@ public class ProxyInfo implements Parcelable {
ProxyInfo p = (ProxyInfo)o;
// If PAC URL is present in either then they must be equal.
// Other parameters will only be for fall back.
- if (!TextUtils.isEmpty(mPacFileUrl)) {
+ if (!Uri.EMPTY.equals(mPacFileUrl)) {
return mPacFileUrl.equals(p.getPacFileUrl()) && mPort == p.mPort;
}
- if (!TextUtils.isEmpty(p.mPacFileUrl)) {
+ if (!Uri.EMPTY.equals(p.mPacFileUrl)) {
+ return false;
+ }
+ if (mExclusionList != null && !mExclusionList.equals(p.getExclusionListAsString())) {
return false;
}
- if (mExclusionList != null && !mExclusionList.equals(p.getExclusionListAsString())) return false;
if (mHost != null && p.getHost() != null && mHost.equals(p.getHost()) == false) {
return false;
}
@@ -296,9 +315,9 @@ public class ProxyInfo implements Parcelable {
* @hide
*/
public void writeToParcel(Parcel dest, int flags) {
- if (mPacFileUrl != null) {
+ if (!Uri.EMPTY.equals(mPacFileUrl)) {
dest.writeByte((byte)1);
- dest.writeString(mPacFileUrl);
+ mPacFileUrl.writeToParcel(dest, 0);
dest.writeInt(mPort);
return;
} else {
@@ -325,7 +344,7 @@ public class ProxyInfo implements Parcelable {
String host = null;
int port = 0;
if (in.readByte() != 0) {
- String url = in.readString();
+ Uri url = Uri.CREATOR.createFromParcel(in);
int localPort = in.readInt();
return new ProxyInfo(url, localPort);
}
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index 1d051dd..ad8e4f7 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -25,22 +25,26 @@ import java.net.Inet4Address;
import java.net.Inet6Address;
import java.util.Collection;
+import java.util.Objects;
/**
- * A simple container for route information.
+ * Represents a network route.
+ * <p>
+ * This is used both to describe static network configuration and live network
+ * configuration information.
*
- * In order to be used, a route must have a destination prefix and:
- *
- * - A gateway address (next-hop, for gatewayed routes), or
- * - An interface (for directly-connected routes), or
- * - Both a gateway and an interface.
- *
- * This class does not enforce these constraints because there is code that
- * uses RouteInfo objects to store directly-connected routes without interfaces.
- * Such objects cannot be used directly, but can be put into a LinkProperties
- * object which then specifies the interface.
- *
- * @hide
+ * A route contains three pieces of information:
+ * <ul>
+ * <li>a destination {@link LinkAddress} for directly-connected subnets. If this is
+ * {@code null} it indicates a default route of the address family (IPv4 or IPv6)
+ * implied by the gateway IP address.
+ * <li>a gateway {@link InetAddress} for default routes. If this is {@code null} it
+ * indicates a directly-connected route.
+ * <li>an interface (which may be unspecified).
+ * </ul>
+ * Either the destination or the gateway may be {@code null}, but not both. If the
+ * destination and gateway are both specified, they must be of the same address family
+ * (IPv4 or IPv6).
*/
public class RouteInfo implements Parcelable {
/**
@@ -67,10 +71,10 @@ public class RouteInfo implements Parcelable {
*
* If destination is null, then gateway must be specified and the
* constructed route is either the IPv4 default route <code>0.0.0.0</code>
- * if @gateway is an instance of {@link Inet4Address}, or the IPv6 default
+ * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default
* route <code>::/0</code> if gateway is an instance of
* {@link Inet6Address}.
- *
+ * <p>
* destination and gateway may not both be null.
*
* @param destination the destination prefix
@@ -102,28 +106,64 @@ public class RouteInfo implements Parcelable {
mDestination = new LinkAddress(NetworkUtils.getNetworkPart(destination.getAddress(),
destination.getNetworkPrefixLength()), destination.getNetworkPrefixLength());
+ if ((destination.getAddress() instanceof Inet4Address &&
+ (gateway instanceof Inet4Address == false)) ||
+ (destination.getAddress() instanceof Inet6Address &&
+ (gateway instanceof Inet6Address == false))) {
+ throw new IllegalArgumentException("address family mismatch in RouteInfo constructor");
+ }
mGateway = gateway;
mInterface = iface;
mIsDefault = isDefault();
mIsHost = isHost();
}
+ /**
+ * Constructs a {@code RouteInfo} object.
+ *
+ * If destination is null, then gateway must be specified and the
+ * constructed route is either the IPv4 default route <code>0.0.0.0</code>
+ * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default
+ * route <code>::/0</code> if gateway is an instance of {@link Inet6Address}.
+ * <p>
+ * Destination and gateway may not both be null.
+ *
+ * @param destination the destination address and prefix in a {@link LinkAddress}
+ * @param gateway the {@link InetAddress} to route packets through
+ */
public RouteInfo(LinkAddress destination, InetAddress gateway) {
this(destination, gateway, null);
}
+ /**
+ * Constructs a default {@code RouteInfo} object.
+ *
+ * @param gateway the {@link InetAddress} to route packets through
+ */
public RouteInfo(InetAddress gateway) {
this(null, gateway, null);
}
- public RouteInfo(LinkAddress host) {
- this(host, null, null);
+ /**
+ * Constructs a {@code RouteInfo} object representing a direct connected subnet.
+ *
+ * @param destination the {@link LinkAddress} describing the address and prefix
+ * length of the subnet.
+ */
+ public RouteInfo(LinkAddress destination) {
+ this(destination, null, null);
}
+ /**
+ * @hide
+ */
public static RouteInfo makeHostRoute(InetAddress host, String iface) {
return makeHostRoute(host, null, iface);
}
+ /**
+ * @hide
+ */
public static RouteInfo makeHostRoute(InetAddress host, InetAddress gateway, String iface) {
if (host == null) return null;
@@ -153,31 +193,108 @@ public class RouteInfo implements Parcelable {
return val;
}
-
+ /**
+ * Retrieves the destination address and prefix length in the form of a {@link LinkAddress}.
+ *
+ * @return {@link LinkAddress} specifying the destination. This is never {@code null}.
+ */
public LinkAddress getDestination() {
return mDestination;
}
+ /**
+ * Retrieves the gateway or next hop {@link InetAddress} for this route.
+ *
+ * @return {@link InetAddress} specifying the gateway or next hop. This may be
+ & {@code null} for a directly-connected route."
+ */
public InetAddress getGateway() {
return mGateway;
}
+ /**
+ * Retrieves the interface used for this route if specified, else {@code null}.
+ *
+ * @return The name of the interface used for this route.
+ */
public String getInterface() {
return mInterface;
}
+ /**
+ * Indicates if this route is a default route (ie, has no destination specified).
+ *
+ * @return {@code true} if the destination has a prefix length of 0.
+ */
public boolean isDefaultRoute() {
return mIsDefault;
}
+ /**
+ * Indicates if this route is a host route (ie, matches only a single host address).
+ *
+ * @return {@code true} if the destination has a prefix length of 32/128 for v4/v6.
+ * @hide
+ */
public boolean isHostRoute() {
return mIsHost;
}
+ /**
+ * Indicates if this route has a next hop ({@code true}) or is directly-connected
+ * ({@code false}).
+ *
+ * @return {@code true} if a gateway is specified
+ * @hide
+ */
public boolean hasGateway() {
return mHasGateway;
}
+ /**
+ * Determines whether the destination and prefix of this route includes the specified
+ * address.
+ *
+ * @param destination A {@link InetAddress} to test to see if it would match this route.
+ * @return {@code true} if the destination and prefix length cover the given address.
+ */
+ public boolean matches(InetAddress destination) {
+ if (destination == null) return false;
+
+ // match the route destination and destination with prefix length
+ InetAddress dstNet = NetworkUtils.getNetworkPart(destination,
+ mDestination.getNetworkPrefixLength());
+
+ return mDestination.getAddress().equals(dstNet);
+ }
+
+ /**
+ * Find the route from a Collection of routes that best matches a given address.
+ * May return null if no routes are applicable.
+ * @param routes a Collection of RouteInfos to chose from
+ * @param dest the InetAddress your trying to get to
+ * @return the RouteInfo from the Collection that best fits the given address
+ *
+ * @hide
+ */
+ public static RouteInfo selectBestRoute(Collection<RouteInfo> routes, InetAddress dest) {
+ if ((routes == null) || (dest == null)) return null;
+
+ RouteInfo bestRoute = null;
+ // pick a longest prefix match under same address type
+ for (RouteInfo route : routes) {
+ if (NetworkUtils.addressTypeMatches(route.mDestination.getAddress(), dest)) {
+ if ((bestRoute != null) &&
+ (bestRoute.mDestination.getNetworkPrefixLength() >=
+ route.mDestination.getNetworkPrefixLength())) {
+ continue;
+ }
+ if (route.matches(dest)) bestRoute = route;
+ }
+ }
+ return bestRoute;
+ }
+
public String toString() {
String val = "";
if (mDestination != null) val = mDestination.toString();
@@ -185,10 +302,37 @@ public class RouteInfo implements Parcelable {
return val;
}
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+
+ if (!(obj instanceof RouteInfo)) return false;
+
+ RouteInfo target = (RouteInfo) obj;
+
+ return Objects.equals(mDestination, target.getDestination()) &&
+ Objects.equals(mGateway, target.getGateway()) &&
+ Objects.equals(mInterface, target.getInterface());
+ }
+
+ public int hashCode() {
+ return (mDestination == null ? 0 : mDestination.hashCode() * 41)
+ + (mGateway == null ? 0 :mGateway.hashCode() * 47)
+ + (mInterface == null ? 0 :mInterface.hashCode() * 67)
+ + (mIsDefault ? 3 : 7);
+ }
+
+ /**
+ * Implement the Parcelable interface
+ * @hide
+ */
public int describeContents() {
return 0;
}
+ /**
+ * Implement the Parcelable interface
+ * @hide
+ */
public void writeToParcel(Parcel dest, int flags) {
if (mDestination == null) {
dest.writeByte((byte) 0);
@@ -208,38 +352,10 @@ public class RouteInfo implements Parcelable {
dest.writeString(mInterface);
}
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
-
- if (!(obj instanceof RouteInfo)) return false;
-
- RouteInfo target = (RouteInfo) obj;
-
- boolean sameDestination = ( mDestination == null) ?
- target.getDestination() == null
- : mDestination.equals(target.getDestination());
-
- boolean sameAddress = (mGateway == null) ?
- target.getGateway() == null
- : mGateway.equals(target.getGateway());
-
- boolean sameInterface = (mInterface == null) ?
- target.getInterface() == null
- : mInterface.equals(target.getInterface());
-
- return sameDestination && sameAddress && sameInterface
- && mIsDefault == target.mIsDefault;
- }
-
- @Override
- public int hashCode() {
- return (mDestination == null ? 0 : mDestination.hashCode() * 41)
- + (mGateway == null ? 0 :mGateway.hashCode() * 47)
- + (mInterface == null ? 0 :mInterface.hashCode() * 67)
- + (mIsDefault ? 3 : 7);
- }
-
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
public static final Creator<RouteInfo> CREATOR =
new Creator<RouteInfo>() {
public RouteInfo createFromParcel(Parcel in) {
@@ -279,39 +395,4 @@ public class RouteInfo implements Parcelable {
return new RouteInfo[size];
}
};
-
- protected boolean matches(InetAddress destination) {
- if (destination == null) return false;
-
- // match the route destination and destination with prefix length
- InetAddress dstNet = NetworkUtils.getNetworkPart(destination,
- mDestination.getNetworkPrefixLength());
-
- return mDestination.getAddress().equals(dstNet);
- }
-
- /**
- * Find the route from a Collection of routes that best matches a given address.
- * May return null if no routes are applicable.
- * @param routes a Collection of RouteInfos to chose from
- * @param dest the InetAddress your trying to get to
- * @return the RouteInfo from the Collection that best fits the given address
- */
- public static RouteInfo selectBestRoute(Collection<RouteInfo> routes, InetAddress dest) {
- if ((routes == null) || (dest == null)) return null;
-
- RouteInfo bestRoute = null;
- // pick a longest prefix match under same address type
- for (RouteInfo route : routes) {
- if (NetworkUtils.addressTypeMatches(route.mDestination.getAddress(), dest)) {
- if ((bestRoute != null) &&
- (bestRoute.mDestination.getNetworkPrefixLength() >=
- route.mDestination.getNetworkPrefixLength())) {
- continue;
- }
- if (route.matches(dest)) bestRoute = route;
- }
- }
- return bestRoute;
- }
}
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 635a50f..9218c11 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -25,7 +25,6 @@ import android.nfc.IAppCallback;
import android.nfc.INfcAdapterExtras;
import android.nfc.INfcTag;
import android.nfc.INfcCardEmulation;
-import android.nfc.INfcUnlockSettings;
import android.os.Bundle;
/**
@@ -36,7 +35,6 @@ interface INfcAdapter
INfcTag getNfcTagInterface();
INfcCardEmulation getNfcCardEmulationInterface();
INfcAdapterExtras getNfcAdapterExtrasInterface(in String pkg);
- INfcUnlockSettings getNfcUnlockSettingsInterface();
int getState();
boolean disable(boolean saveState);
diff --git a/core/java/android/nfc/INfcUnlockSettings.aidl b/core/java/android/nfc/INfcUnlockSettings.aidl
deleted file mode 100644
index 649eeed..0000000
--- a/core/java/android/nfc/INfcUnlockSettings.aidl
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 96a3947..dd8e41c 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -292,7 +292,6 @@ public final class NfcAdapter {
static INfcAdapter sService;
static INfcTag sTagService;
static INfcCardEmulation sCardEmulationService;
- static INfcUnlockSettings sNfcUnlockSettingsService;
/**
* The NfcAdapter object for each application context.
@@ -433,13 +432,6 @@ 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) {
@@ -557,22 +549,6 @@ 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
*/
diff --git a/core/java/android/nfc/NfcUnlock.java b/core/java/android/nfc/NfcUnlock.java
deleted file mode 100644
index 82dcd96..0000000
--- a/core/java/android/nfc/NfcUnlock.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * 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/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java
index b0449224..cabda5d 100644
--- a/core/java/android/nfc/cardemulation/AidGroup.java
+++ b/core/java/android/nfc/cardemulation/AidGroup.java
@@ -2,6 +2,7 @@ package android.nfc.cardemulation;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.List;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -21,6 +22,8 @@ import android.util.Log;
* <p>The format of AIDs is defined in the ISO/IEC 7816-4 specification. This class
* requires the AIDs to be input as a hexadecimal string, with an even amount of
* hexadecimal characters, e.g. "F014811481".
+ *
+ * @hide
*/
public final class AidGroup implements Parcelable {
/**
@@ -30,7 +33,7 @@ public final class AidGroup implements Parcelable {
static final String TAG = "AidGroup";
- final ArrayList<String> aids;
+ final List<String> aids;
final String category;
final String description;
@@ -40,7 +43,7 @@ public final class AidGroup implements Parcelable {
* @param aids The list of AIDs present in the group
* @param category The category of this group, e.g. {@link CardEmulation#CATEGORY_PAYMENT}
*/
- public AidGroup(ArrayList<String> aids, String category) {
+ public AidGroup(List<String> aids, String category) {
if (aids == null || aids.size() == 0) {
throw new IllegalArgumentException("No AIDS in AID group.");
}
@@ -72,7 +75,7 @@ public final class AidGroup implements Parcelable {
/**
* @return the list of AIDs in this group
*/
- public ArrayList<String> getAids() {
+ public List<String> getAids() {
return aids;
}
@@ -121,11 +124,6 @@ public final class AidGroup implements Parcelable {
}
};
- /**
- * @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>();
@@ -152,9 +150,6 @@ public final class AidGroup implements Parcelable {
}
}
- /**
- * @hide
- */
public void writeAsXml(XmlSerializer out) throws IOException {
out.attribute(null, "category", category);
for (String aid : aids) {
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index e24a22a..4b9e890 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -303,12 +303,13 @@ public final class CardEmulation {
}
/**
- * Registers a group of AIDs for the specified service.
+ * Registers a list of AIDs for a specific category for the
+ * specified service.
*
- * <p>If an AID group for that category was previously
+ * <p>If a list of AIDs 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.
+ * that list of AIDs 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 the caller of this API. Typically
@@ -317,10 +318,13 @@ public final class CardEmulation {
* be shared between packages using shared UIDs.
*
* @param service The component name of the service
- * @param aidGroup The group of AIDs to be registered
+ * @param category The category of AIDs to be registered
+ * @param aids A list containing the AIDs to be registered
* @return whether the registration was successful.
*/
- public boolean registerAidGroupForService(ComponentName service, AidGroup aidGroup) {
+ public boolean registerAidsForService(ComponentName service, String category,
+ List<String> aids) {
+ AidGroup aidGroup = new AidGroup(aids, category);
try {
return sService.registerAidGroupForService(UserHandle.myUserId(), service, aidGroup);
} catch (RemoteException e) {
@@ -341,21 +345,24 @@ public final class CardEmulation {
}
/**
- * Retrieves the currently registered AID group for the specified
+ * Retrieves the currently registered AIDs 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
+ * <p>Note that this will only return AIDs that were dynamically
+ * registered using {@link #registerAidsForService(ComponentName, String, List)}
+ * method. It will *not* return AIDs 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
+ * @param category The category for which the AIDs were registered,
+ * e.g. {@link #CATEGORY_PAYMENT}
+ * @return The list of AIDs registered for this category, or null if it couldn't be found.
*/
- public AidGroup getAidGroupForService(ComponentName service, String category) {
+ public List<String> getAidsForService(ComponentName service, String category) {
try {
- return sService.getAidGroupForService(UserHandle.myUserId(), service, category);
+ AidGroup group = sService.getAidGroupForService(UserHandle.myUserId(), service,
+ category);
+ return (group != null ? group.getAids() : null);
} catch (RemoteException e) {
recoverService();
if (sService == null) {
@@ -363,7 +370,9 @@ public final class CardEmulation {
return null;
}
try {
- return sService.getAidGroupForService(UserHandle.myUserId(), service, category);
+ AidGroup group = sService.getAidGroupForService(UserHandle.myUserId(), service,
+ category);
+ return (group != null ? group.getAids() : null);
} catch (RemoteException ee) {
Log.e(TAG, "Failed to recover CardEmulationService.");
return null;
@@ -372,21 +381,21 @@ public final class CardEmulation {
}
/**
- * Removes a registered AID group for the specified category for the
+ * Removes a previously registered list of AIDs 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
+ * <p>Note that this will only remove AIDs that were dynamically
+ * registered using the {@link #registerAidsForService(ComponentName, String, List)}
+ * method. It will *not* remove AIDs that were statically registered in
+ * the manifest. If dynamically registered AIDs are removed using
* this method, and a statically registered AID group for the same category
* exists in the manifest, the static 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}
+ * @param category The category of the AIDs to be removed, e.g. {@link #CATEGORY_PAYMENT}
* @return whether the group was successfully removed.
*/
- public boolean removeAidGroupForService(ComponentName service, String category) {
+ public boolean removeAidsForService(ComponentName service, String category) {
try {
return sService.removeAidGroupForService(UserHandle.myUserId(), service, category);
} catch (RemoteException e) {
diff --git a/core/java/android/os/CommonBundle.java b/core/java/android/os/BaseBundle.java
index e11f170..c2a45ba 100644
--- a/core/java/android/os/CommonBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -18,17 +18,16 @@ 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.Map;
import java.util.Set;
/**
* A mapping from String values to various types.
*/
-abstract class CommonBundle implements Parcelable, Cloneable {
+public class BaseBundle {
private static final String TAG = "Bundle";
static final boolean DEBUG = false;
@@ -64,7 +63,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* inside of the Bundle.
* @param capacity Initial size of the ArrayMap.
*/
- CommonBundle(ClassLoader loader, int capacity) {
+ BaseBundle(ClassLoader loader, int capacity) {
mMap = capacity > 0 ?
new ArrayMap<String, Object>(capacity) : new ArrayMap<String, Object>();
mClassLoader = loader == null ? getClass().getClassLoader() : loader;
@@ -73,7 +72,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
/**
* Constructs a new, empty Bundle.
*/
- CommonBundle() {
+ BaseBundle() {
this((ClassLoader) null, 0);
}
@@ -83,11 +82,11 @@ abstract class CommonBundle implements Parcelable, Cloneable {
*
* @param parcelledData a Parcel containing a Bundle
*/
- CommonBundle(Parcel parcelledData) {
+ BaseBundle(Parcel parcelledData) {
readFromParcelInner(parcelledData);
}
- CommonBundle(Parcel parcelledData, int length) {
+ BaseBundle(Parcel parcelledData, int length) {
readFromParcelInner(parcelledData, length);
}
@@ -98,7 +97,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param loader An explicit ClassLoader to use when instantiating objects
* inside of the Bundle.
*/
- CommonBundle(ClassLoader loader) {
+ BaseBundle(ClassLoader loader) {
this(loader, 0);
}
@@ -108,7 +107,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
*
* @param capacity the initial capacity of the Bundle
*/
- CommonBundle(int capacity) {
+ BaseBundle(int capacity) {
this((ClassLoader) null, capacity);
}
@@ -118,7 +117,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
*
* @param b a Bundle to be copied.
*/
- CommonBundle(CommonBundle b) {
+ BaseBundle(BaseBundle b) {
if (b.mParcelledData != null) {
if (b.mParcelledData == EMPTY_PARCEL) {
mParcelledData = EMPTY_PARCEL;
@@ -149,7 +148,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
*
* @hide
*/
- String getPairValue() {
+ public String getPairValue() {
unparcel();
int size = mMap.size();
if (size > 1) {
@@ -229,7 +228,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
/**
* @hide
*/
- boolean isParcelled() {
+ public boolean isParcelled() {
return mParcelledData != null;
}
@@ -238,7 +237,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
*
* @return the number of mappings as an int.
*/
- int size() {
+ public int size() {
unparcel();
return mMap.size();
}
@@ -246,7 +245,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
/**
* Returns true if the mapping of this Bundle is empty, false otherwise.
*/
- boolean isEmpty() {
+ public boolean isEmpty() {
unparcel();
return mMap.isEmpty();
}
@@ -254,7 +253,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
/**
* Removes all elements from the mapping of this Bundle.
*/
- void clear() {
+ public void clear() {
unparcel();
mMap.clear();
}
@@ -266,7 +265,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String key
* @return true if the key is part of the mapping, false otherwise
*/
- boolean containsKey(String key) {
+ public boolean containsKey(String key) {
unparcel();
return mMap.containsKey(key);
}
@@ -277,7 +276,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String key
* @return an Object, or null
*/
- Object get(String key) {
+ public Object get(String key) {
unparcel();
return mMap.get(key);
}
@@ -287,28 +286,38 @@ abstract class CommonBundle implements Parcelable, Cloneable {
*
* @param key a String key
*/
- void remove(String key) {
+ public void remove(String key) {
unparcel();
mMap.remove(key);
}
/**
- * Inserts all mappings from the given PersistableBundle into this CommonBundle.
+ * Inserts all mappings from the given PersistableBundle into this BaseBundle.
*
* @param bundle a PersistableBundle
*/
- void putAll(PersistableBundle bundle) {
+ public void putAll(PersistableBundle bundle) {
unparcel();
bundle.unparcel();
mMap.putAll(bundle.mMap);
}
/**
+ * Inserts all mappings from the given Map into this BaseBundle.
+ *
+ * @param map a Map
+ */
+ void putAll(Map map) {
+ unparcel();
+ mMap.putAll(map);
+ }
+
+ /**
* Returns a Set containing the Strings used as keys in this Bundle.
*
* @return a Set of String keys
*/
- Set<String> keySet() {
+ public Set<String> keySet() {
unparcel();
return mMap.keySet();
}
@@ -368,7 +377,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value an int, or null
*/
- void putInt(String key, int value) {
+ public void putInt(String key, int value) {
unparcel();
mMap.put(key, value);
}
@@ -380,7 +389,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a long
*/
- void putLong(String key, long value) {
+ public void putLong(String key, long value) {
unparcel();
mMap.put(key, value);
}
@@ -404,7 +413,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a double
*/
- void putDouble(String key, double value) {
+ public void putDouble(String key, double value) {
unparcel();
mMap.put(key, value);
}
@@ -416,7 +425,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a String, or null
*/
- void putString(String key, String value) {
+ public void putString(String key, String value) {
unparcel();
mMap.put(key, value);
}
@@ -536,7 +545,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value an int array object, or null
*/
- void putIntArray(String key, int[] value) {
+ public void putIntArray(String key, int[] value) {
unparcel();
mMap.put(key, value);
}
@@ -548,7 +557,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a long array object, or null
*/
- void putLongArray(String key, long[] value) {
+ public void putLongArray(String key, long[] value) {
unparcel();
mMap.put(key, value);
}
@@ -572,7 +581,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a double array object, or null
*/
- void putDoubleArray(String key, double[] value) {
+ public void putDoubleArray(String key, double[] value) {
unparcel();
mMap.put(key, value);
}
@@ -584,7 +593,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a String array object, or null
*/
- void putStringArray(String key, String[] value) {
+ public void putStringArray(String key, String[] value) {
unparcel();
mMap.put(key, value);
}
@@ -602,18 +611,6 @@ abstract class CommonBundle 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
- */
- 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.
*
@@ -780,7 +777,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String
* @return an int value
*/
- int getInt(String key) {
+ public int getInt(String key) {
unparcel();
return getInt(key, 0);
}
@@ -793,7 +790,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param defaultValue Value to return if key does not exist
* @return an int value
*/
- int getInt(String key, int defaultValue) {
+ public int getInt(String key, int defaultValue) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -814,7 +811,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String
* @return a long value
*/
- long getLong(String key) {
+ public long getLong(String key) {
unparcel();
return getLong(key, 0L);
}
@@ -827,7 +824,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param defaultValue Value to return if key does not exist
* @return a long value
*/
- long getLong(String key, long defaultValue) {
+ public long getLong(String key, long defaultValue) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -882,7 +879,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String
* @return a double value
*/
- double getDouble(String key) {
+ public double getDouble(String key) {
unparcel();
return getDouble(key, 0.0);
}
@@ -895,7 +892,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param defaultValue Value to return if key does not exist
* @return a double value
*/
- double getDouble(String key, double defaultValue) {
+ public double getDouble(String key, double defaultValue) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -917,7 +914,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a String value, or null
*/
- String getString(String key) {
+ public String getString(String key) {
unparcel();
final Object o = mMap.get(key);
try {
@@ -937,7 +934,7 @@ abstract class CommonBundle 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.
*/
- String getString(String key, String defaultValue) {
+ public String getString(String key, String defaultValue) {
final String s = getString(key);
return (s == null) ? defaultValue : s;
}
@@ -981,28 +978,6 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* 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) {
@@ -1181,7 +1156,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return an int[] value, or null
*/
- int[] getIntArray(String key) {
+ public int[] getIntArray(String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1203,7 +1178,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a long[] value, or null
*/
- long[] getLongArray(String key) {
+ public long[] getLongArray(String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1247,7 +1222,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a double[] value, or null
*/
- double[] getDoubleArray(String key) {
+ public double[] getDoubleArray(String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1269,7 +1244,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a String[] value, or null
*/
- String[] getStringArray(String key) {
+ public String[] getStringArray(String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index f339e52..32050dc 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -146,9 +146,9 @@ public class BatteryManager {
return null;
}
- BatteryProperty prop = new BatteryProperty(Integer.MIN_VALUE);
+ BatteryProperty prop = new BatteryProperty();
if ((mBatteryPropertiesRegistrar.getProperty(id, prop) == 0) &&
- (prop.getInt() != Integer.MIN_VALUE))
+ (prop.getLong() != Long.MIN_VALUE))
return prop;
else
return null;
diff --git a/core/java/android/os/BatteryProperty.java b/core/java/android/os/BatteryProperty.java
index ec73952..27dad4f 100644
--- a/core/java/android/os/BatteryProperty.java
+++ b/core/java/android/os/BatteryProperty.java
@@ -53,20 +53,18 @@ public class BatteryProperty implements Parcelable {
*/
public static final int CAPACITY = 4;
- private int mValueInt;
-
/**
- * @hide
+ * Battery remaining energy in nanowatt-hours, as a long integer.
*/
- public BatteryProperty(int value) {
- mValueInt = value;
- }
+ public static final int ENERGY_COUNTER = 5;
+
+ private long mValueLong;
/**
* @hide
*/
public BatteryProperty() {
- mValueInt = Integer.MIN_VALUE;
+ mValueLong = Long.MIN_VALUE;
}
/**
@@ -79,9 +77,21 @@ public class BatteryProperty implements Parcelable {
* @return The queried property value, or Integer.MIN_VALUE if not supported.
*/
public int getInt() {
- return mValueInt;
+ return (int)mValueLong;
}
+ /**
+ * Return the value of a property of long type previously queried
+ * via {@link BatteryManager#getProperty
+ * BatteryManager.getProperty()}. If the platform does
+ * not provide the property queried, this value will be
+ * Long.MIN_VALUE.
+ *
+ * @return The queried property value, or Long.MIN_VALUE if not supported.
+ */
+ public long getLong() {
+ return mValueLong;
+ }
/*
* Parcel read/write code must be kept in sync with
* frameworks/native/services/batteryservice/BatteryProperty.cpp
@@ -92,11 +102,11 @@ public class BatteryProperty implements Parcelable {
}
public void readFromParcel(Parcel p) {
- mValueInt = p.readInt();
+ mValueLong = p.readLong();
}
public void writeToParcel(Parcel p, int flags) {
- p.writeInt(mValueInt);
+ p.writeLong(mValueLong);
}
public static final Parcelable.Creator<BatteryProperty> CREATOR
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 8b7467f..e627d49 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Formatter;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -30,6 +31,7 @@ import android.telephony.SignalStrength;
import android.text.format.DateFormat;
import android.util.Printer;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.util.TimeUtils;
import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper;
@@ -529,7 +531,8 @@ public abstract class BatteryStats implements Parcelable {
public final static class HistoryItem implements Parcelable {
public HistoryItem next;
-
+
+ // The time of this event in milliseconds, as per SystemClock.elapsedRealtime().
public long time;
public static final byte CMD_UPDATE = 0; // These can be written as deltas
@@ -537,6 +540,7 @@ public abstract class BatteryStats implements Parcelable {
public static final byte CMD_START = 4;
public static final byte CMD_CURRENT_TIME = 5;
public static final byte CMD_OVERFLOW = 6;
+ public static final byte CMD_RESET = 7;
public byte cmd = CMD_NULL;
@@ -597,6 +601,7 @@ public abstract class BatteryStats implements Parcelable {
public int states;
public static final int STATE2_VIDEO_ON_FLAG = 1<<0;
+ public static final int STATE2_LOW_POWER_FLAG = 1<<1;
public int states2;
// The wake lock that was acquired at this point.
@@ -618,8 +623,13 @@ public abstract class BatteryStats implements Parcelable {
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;
+ // Events for all additional wake locks aquired/release within a wake block.
+ // These are not generated by default.
+ public static final int EVENT_WAKE_LOCK = 0x0005;
// Number of event types.
- public static final int EVENT_COUNT = 0x0005;
+ public static final int EVENT_COUNT = 0x0006;
+ // Mask to extract out only the type part of the event.
+ public static final int EVENT_TYPE_MASK = ~(EVENT_FLAG_START|EVENT_FLAG_FINISH);
public static final int EVENT_PROC_START = EVENT_PROC | EVENT_FLAG_START;
public static final int EVENT_PROC_FINISH = EVENT_PROC | EVENT_FLAG_FINISH;
@@ -629,12 +639,14 @@ public abstract class BatteryStats implements Parcelable {
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;
+ public static final int EVENT_WAKE_LOCK_START = EVENT_WAKE_LOCK | EVENT_FLAG_START;
+ public static final int EVENT_WAKE_LOCK_FINISH = EVENT_WAKE_LOCK | EVENT_FLAG_FINISH;
// For CMD_EVENT.
public int eventCode;
public HistoryTag eventTag;
- // Only set for CMD_CURRENT_TIME.
+ // Only set for CMD_CURRENT_TIME or CMD_RESET, as per System.currentTimeMillis().
public long currentTime;
// Meta-data when reading.
@@ -684,7 +696,7 @@ public abstract class BatteryStats implements Parcelable {
dest.writeInt(eventCode);
eventTag.writeToParcel(dest, flags);
}
- if (cmd == CMD_CURRENT_TIME) {
+ if (cmd == CMD_CURRENT_TIME || cmd == CMD_RESET) {
dest.writeLong(currentTime);
}
}
@@ -722,7 +734,7 @@ public abstract class BatteryStats implements Parcelable {
eventCode = EVENT_NONE;
eventTag = null;
}
- if (cmd == CMD_CURRENT_TIME) {
+ if (cmd == CMD_CURRENT_TIME || cmd == CMD_RESET) {
currentTime = src.readLong();
} else {
currentTime = 0;
@@ -833,7 +845,64 @@ public abstract class BatteryStats implements Parcelable {
return true;
}
}
-
+
+ public final static class HistoryEventTracker {
+ private final HashMap<String, SparseIntArray>[] mActiveEvents
+ = (HashMap<String, SparseIntArray>[]) new HashMap[HistoryItem.EVENT_COUNT];
+
+ public boolean updateState(int code, String name, int uid, int poolIdx) {
+ if ((code&HistoryItem.EVENT_FLAG_START) != 0) {
+ int idx = code&HistoryItem.EVENT_TYPE_MASK;
+ HashMap<String, SparseIntArray> active = mActiveEvents[idx];
+ if (active == null) {
+ active = new HashMap<String, SparseIntArray>();
+ mActiveEvents[idx] = active;
+ }
+ SparseIntArray uids = active.get(name);
+ if (uids == null) {
+ uids = new SparseIntArray();
+ active.put(name, uids);
+ }
+ if (uids.indexOfKey(uid) >= 0) {
+ // Already set, nothing to do!
+ return false;
+ }
+ uids.put(uid, poolIdx);
+ } else if ((code&HistoryItem.EVENT_FLAG_FINISH) != 0) {
+ int idx = code&HistoryItem.EVENT_TYPE_MASK;
+ HashMap<String, SparseIntArray> active = mActiveEvents[idx];
+ if (active == null) {
+ // not currently active, nothing to do.
+ return false;
+ }
+ SparseIntArray uids = active.get(name);
+ if (uids == null) {
+ // not currently active, nothing to do.
+ return false;
+ }
+ idx = uids.indexOfKey(uid);
+ if (idx < 0) {
+ // not currently active, nothing to do.
+ return false;
+ }
+ uids.removeAt(idx);
+ if (uids.size() <= 0) {
+ active.remove(name);
+ }
+ }
+ return true;
+ }
+
+ public void removeEvents(int code) {
+ int idx = code&HistoryItem.EVENT_TYPE_MASK;
+ mActiveEvents[idx] = null;
+ }
+
+ public HashMap<String, SparseIntArray> getStateForEvent(int code) {
+ return mActiveEvents[code];
+ }
+ }
+
public static final class BitDescription {
public final int mask;
public final int shift;
@@ -861,7 +930,15 @@ public abstract class BatteryStats implements Parcelable {
this.shortValues = shortValues;
}
}
-
+
+ /**
+ * Don't allow any more batching in to the current history event. This
+ * is called when printing partial histories, so to ensure that the next
+ * history event will go in to a new batch after what was printed in the
+ * last partial history.
+ */
+ public abstract void commitCurrentHistoryBatchLocked();
+
public abstract int getHistoryTotalSize();
public abstract int getHistoryUsedSize();
@@ -939,6 +1016,21 @@ public abstract class BatteryStats implements Parcelable {
long elapsedRealtimeUs, int which);
/**
+ * Returns the time in microseconds that low power mode has been enabled while the device was
+ * running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getLowPowerModeEnabledTime(long elapsedRealtimeUs, int which);
+
+ /**
+ * Returns the number of times that low power mode was enabled.
+ *
+ * {@hide}
+ */
+ public abstract int getLowPowerModeEnabledCount(int which);
+
+ /**
* Returns the time in microseconds that the phone has been on while the device was
* running on battery.
*
@@ -1099,14 +1191,15 @@ public abstract class BatteryStats implements Parcelable {
public static final BitDescription[] HISTORY_STATE2_DESCRIPTIONS
= new BitDescription[] {
new BitDescription(HistoryItem.STATE2_VIDEO_ON_FLAG, "video", "v"),
+ new BitDescription(HistoryItem.STATE2_LOW_POWER_FLAG, "low_power", "lp"),
};
public static final String[] HISTORY_EVENT_NAMES = new String[] {
- "null", "proc", "fg", "top", "sync"
+ "null", "proc", "fg", "top", "sync", "wake_lock_in"
};
public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
- "Enl", "Epr", "Efg", "Etp", "Esy"
+ "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl"
};
/**
@@ -1572,11 +1665,12 @@ public abstract class BatteryStats implements Parcelable {
final long totalUptime = computeUptime(rawUptime, which);
final long screenOnTime = getScreenOnTime(rawRealtime, which);
final long interactiveTime = getInteractiveTime(rawRealtime, which);
+ final long lowPowerModeEnabledTime = getLowPowerModeEnabledTime(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);
SparseArray<? extends Uid> uidStats = getUidStats();
@@ -1641,7 +1735,8 @@ public abstract class BatteryStats implements Parcelable {
mobileRxTotalBytes, mobileTxTotalBytes, wifiRxTotalBytes, wifiTxTotalBytes,
fullWakeLockTimeTotal, partialWakeLockTimeTotal,
0 /*legacy input event count*/, getMobileRadioActiveTime(rawRealtime, which),
- getMobileRadioActiveAdjustedTime(which), interactiveTime / 1000);
+ getMobileRadioActiveAdjustedTime(which), interactiveTime / 1000,
+ lowPowerModeEnabledTime / 1000);
// Dump screen brightness stats
Object[] args = new Object[NUM_SCREEN_BRIGHTNESS_BINS];
@@ -2034,32 +2129,20 @@ public abstract class BatteryStats implements Parcelable {
final long screenOnTime = getScreenOnTime(rawRealtime, which);
final long interactiveTime = getInteractiveTime(rawRealtime, which);
+ final long lowPowerModeEnabledTime = getLowPowerModeEnabledTime(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(" Interactive: "); formatTimeMs(sb, interactiveTime / 1000);
- sb.append("("); sb.append(formatRatioLocked(interactiveTime, whichBatteryRealtime));
- sb.append(")");
- pw.println(sb.toString());
- sb.setLength(0);
- sb.append(prefix);
sb.append(" Screen on: "); formatTimeMs(sb, screenOnTime / 1000);
sb.append("("); sb.append(formatRatioLocked(screenOnTime, whichBatteryRealtime));
sb.append(") "); sb.append(getScreenOnCount(which));
- sb.append("x, Active phone call: "); formatTimeMs(sb, phoneOnTime / 1000);
- sb.append("("); sb.append(formatRatioLocked(phoneOnTime, whichBatteryRealtime));
+ sb.append("x, Interactive: "); formatTimeMs(sb, interactiveTime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(interactiveTime, whichBatteryRealtime));
sb.append(")");
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:");
@@ -2081,7 +2164,24 @@ public abstract class BatteryStats implements Parcelable {
}
if (!didOne) sb.append(" (no activity)");
pw.println(sb.toString());
-
+ if (lowPowerModeEnabledTime != 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Low power mode enabled: ");
+ formatTimeMs(sb, lowPowerModeEnabledTime / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(lowPowerModeEnabledTime, whichBatteryRealtime));
+ sb.append(")");
+ 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));
+ }
+
// Calculate wakelock times across all uids.
long fullWakeLockTimeTotalMicros = 0;
long partialWakeLockTimeTotalMicros = 0;
@@ -2351,8 +2451,10 @@ public abstract class BatteryStats implements Parcelable {
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.print(", actual drain: "); printmAh(pw, helper.getMinDrainedPower());
+ if (helper.getMinDrainedPower() != helper.getMaxDrainedPower()) {
+ pw.print("-"); printmAh(pw, helper.getMaxDrainedPower());
+ }
pw.println();
for (int i=0; i<sippers.size(); i++) {
BatterySipper bs = sippers.get(i);
@@ -2946,6 +3048,8 @@ public abstract class BatteryStats implements Parcelable {
pw.print(rec.numReadInts);
pw.print(") ");
} else {
+ pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
+ pw.print(HISTORY_DATA); pw.print(',');
if (lastTime < 0) {
pw.print(rec.time - baseTime);
} else {
@@ -2958,10 +3062,14 @@ public abstract class BatteryStats implements Parcelable {
pw.print(":");
}
pw.println("START");
- } else if (rec.cmd == HistoryItem.CMD_CURRENT_TIME) {
+ } else if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
+ || rec.cmd == HistoryItem.CMD_RESET) {
if (checkin) {
pw.print(":");
}
+ if (rec.cmd == HistoryItem.CMD_RESET) {
+ pw.print("RESET:");
+ }
pw.print("TIME:");
if (checkin) {
pw.println(rec.currentTime);
@@ -3128,6 +3236,7 @@ public abstract class BatteryStats implements Parcelable {
}
pw.println();
oldState = rec.states;
+ oldState2 = rec.states2;
}
}
}
@@ -3187,6 +3296,89 @@ public abstract class BatteryStats implements Parcelable {
public static final int DUMP_INCLUDE_HISTORY = 1<<3;
public static final int DUMP_VERBOSE = 1<<4;
+ private void dumpHistoryLocked(PrintWriter pw, int flags, long histStart, boolean checkin) {
+ final HistoryPrinter hprinter = new HistoryPrinter();
+ final HistoryItem rec = new HistoryItem();
+ long lastTime = -1;
+ long baseTime = -1;
+ boolean printed = false;
+ HistoryEventTracker tracker = null;
+ 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
+ || rec.cmd == HistoryItem.CMD_RESET
+ || rec.cmd == HistoryItem.CMD_START) {
+ printed = true;
+ hprinter.printNextItem(pw, rec, baseTime, checkin,
+ (flags&DUMP_VERBOSE) != 0);
+ rec.cmd = HistoryItem.CMD_UPDATE;
+ } else if (rec.currentTime != 0) {
+ printed = true;
+ byte cmd = rec.cmd;
+ rec.cmd = HistoryItem.CMD_CURRENT_TIME;
+ hprinter.printNextItem(pw, rec, baseTime, checkin,
+ (flags&DUMP_VERBOSE) != 0);
+ rec.cmd = cmd;
+ }
+ if (tracker != null) {
+ if (rec.cmd != HistoryItem.CMD_UPDATE) {
+ hprinter.printNextItem(pw, rec, baseTime, checkin,
+ (flags&DUMP_VERBOSE) != 0);
+ rec.cmd = HistoryItem.CMD_UPDATE;
+ }
+ int oldEventCode = rec.eventCode;
+ HistoryTag oldEventTag = rec.eventTag;
+ rec.eventTag = new HistoryTag();
+ for (int i=0; i<HistoryItem.EVENT_COUNT; i++) {
+ HashMap<String, SparseIntArray> active
+ = tracker.getStateForEvent(i);
+ if (active == null) {
+ continue;
+ }
+ for (HashMap.Entry<String, SparseIntArray> ent
+ : active.entrySet()) {
+ SparseIntArray uids = ent.getValue();
+ for (int j=0; j<uids.size(); j++) {
+ rec.eventCode = i;
+ rec.eventTag.string = ent.getKey();
+ rec.eventTag.uid = uids.keyAt(j);
+ rec.eventTag.poolIdx = uids.valueAt(j);
+ hprinter.printNextItem(pw, rec, baseTime, checkin,
+ (flags&DUMP_VERBOSE) != 0);
+ rec.wakeReasonTag = null;
+ rec.wakelockTag = null;
+ }
+ }
+ }
+ rec.eventCode = oldEventCode;
+ rec.eventTag = oldEventTag;
+ tracker = null;
+ }
+ }
+ hprinter.printNextItem(pw, rec, baseTime, checkin,
+ (flags&DUMP_VERBOSE) != 0);
+ } else if (false && rec.eventCode != HistoryItem.EVENT_NONE) {
+ // This is an attempt to aggregate the previous state and generate
+ // fake events to reflect that state at the point where we start
+ // printing real events. It doesn't really work right, so is turned off.
+ if (tracker == null) {
+ tracker = new HistoryEventTracker();
+ }
+ tracker.updateState(rec.eventCode, rec.eventTag.string,
+ rec.eventTag.uid, rec.eventTag.poolIdx);
+ }
+ }
+ if (histStart >= 0) {
+ commitCurrentHistoryBatchLocked();
+ pw.print(checkin ? "NEXT: " : " NEXT: "); pw.println(lastTime+1);
+ }
+ }
+
/**
* Dumps a human-readable summary of the battery statistics to the given PrintWriter.
*
@@ -3200,9 +3392,6 @@ public abstract class BatteryStats implements Parcelable {
(flags&(DUMP_HISTORY_ONLY|DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY)) != 0;
if ((flags&DUMP_HISTORY_ONLY) != 0 || !filtering) {
- long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
-
- final HistoryItem rec = new HistoryItem();
final long historyTotalSize = getHistoryTotalSize();
final long historyUsedSize = getHistoryUsedSize();
if (startIteratingHistoryLocked()) {
@@ -3218,35 +3407,7 @@ public abstract class BatteryStats implements Parcelable {
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);
- }
+ dumpHistoryLocked(pw, flags, histStart, false);
pw.println();
} finally {
finishIteratingHistoryLocked();
@@ -3255,6 +3416,7 @@ public abstract class BatteryStats implements Parcelable {
if (startIteratingOldHistoryLocked()) {
try {
+ final HistoryItem rec = new HistoryItem();
pw.println("Old battery History:");
HistoryPrinter hprinter = new HistoryPrinter();
long baseTime = -1;
@@ -3348,7 +3510,6 @@ public abstract class BatteryStats implements Parcelable {
(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()) {
try {
for (int i=0; i<getHistoryStringPoolSize(); i++) {
@@ -3365,37 +3526,7 @@ public abstract class BatteryStats implements Parcelable {
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);
- }
+ dumpHistoryLocked(pw, flags, histStart, true);
} finally {
finishIteratingHistoryLocked();
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 1ca6b90..a7485b4 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -88,7 +88,8 @@ public class Build {
*
* @hide
*/
- public static final String[] SUPPORTED_ABIS = getString("ro.product.cpu.abilist").split(",");
+ public static final String[] SUPPORTED_ABIS = SystemProperties.get("ro.product.cpu.abilist")
+ .split(",");
/**
* An ordered list of <b>32 bit</b> ABIs supported by this device. The most preferred ABI
@@ -98,8 +99,8 @@ public class Build {
*
* @hide
*/
- public static final String[] SUPPORTED_32_BIT_ABIS = getString("ro.product.cpu.abilist32")
- .split(",");
+ public static final String[] SUPPORTED_32_BIT_ABIS =
+ SystemProperties.get("ro.product.cpu.abilist32").split(",");
/**
* An ordered list of <b>64 bit</b> ABIs supported by this device. The most preferred ABI
@@ -109,8 +110,8 @@ public class Build {
*
* @hide
*/
- public static final String[] SUPPORTED_64_BIT_ABIS = getString("ro.product.cpu.abilist64")
- .split(",");
+ public static final String[] SUPPORTED_64_BIT_ABIS =
+ SystemProperties.get("ro.product.cpu.abilist64").split(",");
/** Various version strings. */
@@ -515,9 +516,16 @@ public class Build {
public static final int KITKAT = 19;
/**
- * Android 4.5: KitKat for watches, snacks on the run.
+ * Android 4.4W: KitKat for watches, snacks on the run.
+ *
+ * <p>Applications targeting this or a later release will get these
+ * new changes in behavior:</p>
+ * <ul>
+ * <li>{@link android.app.AlertDialog} might not have a default background if the theme does
+ * not specify one.</li>
+ * </ul>
*/
- public static final int KITKAT_WATCH = CUR_DEVELOPMENT; // STOPSHIP: update API level
+ public static final int KITKAT_WATCH = 20;
/**
* L!
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index c85e418..e42c3fe 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -28,14 +28,14 @@ import java.util.Set;
* A mapping from String values to various Parcelable types.
*
*/
-public final class Bundle extends CommonBundle {
+public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
public static final Bundle EMPTY;
static final Parcel EMPTY_PARCEL;
static {
EMPTY = new Bundle();
EMPTY.mMap = ArrayMap.EMPTY;
- EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL;
+ EMPTY_PARCEL = BaseBundle.EMPTY_PARCEL;
}
private boolean mHasFds = false;
@@ -125,14 +125,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * @hide
- */
- @Override
- public String getPairValue() {
- return super.getPairValue();
- }
-
- /**
* Changes the ClassLoader this Bundle uses when instantiating objects.
*
* @param loader An explicit ClassLoader to use when instantiating objects
@@ -168,32 +160,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * @hide
- */
- @Override
- public boolean isParcelled() {
- return super.isParcelled();
- }
-
- /**
- * Returns the number of mappings contained in this Bundle.
- *
- * @return the number of mappings as an int.
- */
- @Override
- public int size() {
- return super.size();
- }
-
- /**
- * Returns true if the mapping of this Bundle is empty, false otherwise.
- */
- @Override
- public boolean isEmpty() {
- return super.isEmpty();
- }
-
- /**
* Removes all elements from the mapping of this Bundle.
*/
@Override
@@ -205,39 +171,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * 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
- */
- @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 Bundle.
- *
- * @param key a String key
- */
- @Override
- public void remove(String key) {
- super.remove(key);
- }
-
- /**
* Inserts all mappings from the given Bundle into this Bundle.
*
* @param bundle a Bundle
@@ -253,25 +186,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * 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 Bundle.
- *
- * @return a Set of String keys
- */
- @Override
- public Set<String> keySet() {
- return super.keySet();
- }
-
- /**
* Reports whether the bundle contains any parcelled file descriptors.
*/
public boolean hasFileDescriptors() {
@@ -384,30 +298,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * 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
- */
- @Override
- public void putInt(String key, int value) {
- super.putInt(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
- */
- @Override
- public void putLong(String key, long value) {
- super.putLong(key, value);
- }
-
- /**
* Inserts a float value into the mapping of this Bundle, replacing
* any existing value for the given key.
*
@@ -420,30 +310,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * 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
- */
- @Override
- public void putDouble(String key, double value) {
- super.putDouble(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
- */
- @Override
- public void putString(String key, String value) {
- super.putString(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.
*
@@ -616,30 +482,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * 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
- */
- @Override
- public void putIntArray(String key, int[] value) {
- super.putIntArray(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
- */
- @Override
- public void putLongArray(String key, long[] value) {
- super.putLongArray(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.
*
@@ -652,30 +494,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * 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
- */
- @Override
- public void putDoubleArray(String key, double[] value) {
- super.putDoubleArray(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
- */
- @Override
- public void putStringArray(String key, String[] value) {
- super.putStringArray(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.
*
@@ -700,17 +518,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * 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.
*
@@ -846,56 +653,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * 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.0f if
* no mapping of the desired type exists for the given key.
*
@@ -921,58 +678,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * 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.
@@ -1027,18 +732,6 @@ public final class Bundle extends CommonBundle {
* 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) {
@@ -1232,32 +925,6 @@ public final class Bundle extends CommonBundle {
* 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 float[] value, or null
*/
@Override
@@ -1271,32 +938,6 @@ public final class Bundle extends CommonBundle {
* 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);
- }
-
- /**
- * 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
*/
@Override
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index e98a26b..e84b695 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -191,6 +191,10 @@ public class Environment {
return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_MEDIA, packageName);
}
+ public File[] buildExternalStorageAppMediaDirsForVold(String packageName) {
+ return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_MEDIA, packageName);
+ }
+
public File[] buildExternalStorageAppObbDirs(String packageName) {
return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB, packageName);
}
diff --git a/core/java/android/os/FileBridge.java b/core/java/android/os/FileBridge.java
new file mode 100644
index 0000000..7f8bc9f
--- /dev/null
+++ b/core/java/android/os/FileBridge.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.SOCK_STREAM;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import libcore.io.IoBridge;
+import libcore.io.IoUtils;
+import libcore.io.Memory;
+import libcore.io.Streams;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.SyncFailedException;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * Simple bridge that allows file access across process boundaries without
+ * returning the underlying {@link FileDescriptor}. This is useful when the
+ * server side needs to strongly assert that a client side is completely
+ * hands-off.
+ *
+ * @hide
+ */
+public class FileBridge extends Thread {
+ private static final String TAG = "FileBridge";
+
+ // TODO: consider extending to support bidirectional IO
+
+ private static final int MSG_LENGTH = 8;
+
+ /** CMD_WRITE [len] [data] */
+ private static final int CMD_WRITE = 1;
+ /** CMD_FSYNC */
+ private static final int CMD_FSYNC = 2;
+
+ private FileDescriptor mTarget;
+
+ private final FileDescriptor mServer = new FileDescriptor();
+ private final FileDescriptor mClient = new FileDescriptor();
+
+ private volatile boolean mClosed;
+
+ public FileBridge() {
+ try {
+ Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mServer, mClient);
+ } catch (ErrnoException e) {
+ throw new RuntimeException("Failed to create bridge");
+ }
+ }
+
+ public boolean isClosed() {
+ return mClosed;
+ }
+
+ public void setTargetFile(FileDescriptor target) {
+ mTarget = target;
+ }
+
+ public FileDescriptor getClientSocket() {
+ return mClient;
+ }
+
+ @Override
+ public void run() {
+ final byte[] temp = new byte[8192];
+ try {
+ while (IoBridge.read(mServer, temp, 0, MSG_LENGTH) == MSG_LENGTH) {
+ final int cmd = Memory.peekInt(temp, 0, ByteOrder.BIG_ENDIAN);
+
+ if (cmd == CMD_WRITE) {
+ // Shuttle data into local file
+ int len = Memory.peekInt(temp, 4, ByteOrder.BIG_ENDIAN);
+ while (len > 0) {
+ int n = IoBridge.read(mServer, temp, 0, Math.min(temp.length, len));
+ IoBridge.write(mTarget, temp, 0, n);
+ len -= n;
+ }
+
+ } else if (cmd == CMD_FSYNC) {
+ // Sync and echo back to confirm
+ Os.fsync(mTarget);
+ IoBridge.write(mServer, temp, 0, MSG_LENGTH);
+ }
+ }
+
+ // Client was closed; one last fsync
+ Os.fsync(mTarget);
+
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed during bridge: ", e);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed during bridge: ", e);
+ } finally {
+ IoUtils.closeQuietly(mTarget);
+ IoUtils.closeQuietly(mServer);
+ IoUtils.closeQuietly(mClient);
+ mClosed = true;
+ }
+ }
+
+ public static class FileBridgeOutputStream extends OutputStream {
+ private final FileDescriptor mClient;
+ private final byte[] mTemp = new byte[MSG_LENGTH];
+
+ public FileBridgeOutputStream(FileDescriptor client) {
+ mClient = client;
+ }
+
+ @Override
+ public void close() throws IOException {
+ IoBridge.closeAndSignalBlockedThreads(mClient);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ Memory.pokeInt(mTemp, 0, CMD_FSYNC, ByteOrder.BIG_ENDIAN);
+ IoBridge.write(mClient, mTemp, 0, MSG_LENGTH);
+
+ // Wait for server to ack
+ if (IoBridge.read(mClient, mTemp, 0, MSG_LENGTH) == MSG_LENGTH) {
+ if (Memory.peekInt(mTemp, 0, ByteOrder.BIG_ENDIAN) == CMD_FSYNC) {
+ return;
+ }
+ }
+
+ throw new SyncFailedException("Failed to fsync() across bridge");
+ }
+
+ @Override
+ public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount);
+ Memory.pokeInt(mTemp, 0, CMD_WRITE, ByteOrder.BIG_ENDIAN);
+ Memory.pokeInt(mTemp, 4, byteCount, ByteOrder.BIG_ENDIAN);
+ IoBridge.write(mClient, mTemp, 0, MSG_LENGTH);
+ IoBridge.write(mClient, buffer, byteOffset, byteCount);
+ }
+
+ @Override
+ public void write(int oneByte) throws IOException {
+ Streams.writeSingleByte(this, oneByte);
+ }
+ }
+}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index d71c3e6..1dba77d 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -17,6 +17,7 @@
package android.os;
import android.system.ErrnoException;
+import android.text.TextUtils;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
@@ -382,4 +383,32 @@ public class FileUtils {
}
return filePath.startsWith(dirPath);
}
+
+ public static void deleteContents(File dir) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteContents(file);
+ }
+ file.delete();
+ }
+ }
+ }
+
+ /**
+ * Assert that given filename is valid on ext4.
+ */
+ public static boolean isValidExtFilename(String name) {
+ if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
+ return false;
+ }
+ for (int i = 0; i < name.length(); i++) {
+ final char c = name.charAt(i);
+ if (c == '\0' || c == '/') {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index f5ff185..eb9ba13 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -99,24 +99,12 @@ interface INetworkManagementService
/**
* Add the specified route to the interface.
*/
- void addRoute(String iface, in RouteInfo route);
+ void addRoute(int netId, in RouteInfo route);
/**
* Remove the specified route from the interface.
*/
- void removeRoute(String iface, in RouteInfo route);
-
- /**
- * Add the specified route to a secondary interface
- * This will go into a special route table to be accessed
- * via ip rules
- */
- void addSecondaryRoute(String iface, in RouteInfo route);
-
- /**
- * Remove the specified secondary route.
- */
- void removeSecondaryRoute(String iface, in RouteInfo route);
+ void removeRoute(int netId, in RouteInfo route);
/**
* Set the specified MTU size
@@ -320,24 +308,14 @@ interface INetworkManagementService
void removeIdleTimer(String iface);
/**
- * Sets the name of the default interface in the DNS resolver.
- */
- void setDefaultInterfaceForDns(String iface);
-
- /**
- * Bind name servers to an interface in the DNS resolver.
- */
- void setDnsServersForInterface(String iface, in String[] servers, String domains);
-
- /**
- * Flush the DNS cache associated with the default interface.
+ * Bind name servers to a network in the DNS resolver.
*/
- void flushDefaultDnsCache();
+ void setDnsServersForNetwork(int netId, in String[] servers, String domains);
/**
- * Flush the DNS cache associated with the specified interface.
+ * Flush the DNS cache associated with the specified network.
*/
- void flushInterfaceDnsCache(String iface);
+ void flushNetworkDnsCache(int netId);
void setFirewallEnabled(boolean enabled);
boolean isFirewallEnabled();
@@ -350,7 +328,7 @@ interface INetworkManagementService
* Set all packets from users [uid_start,uid_end] to go through interface iface
* iface must already be set for marked forwarding by {@link setMarkedForwarding}
*/
- void setUidRangeRoute(String iface, int uid_start, int uid_end);
+ void setUidRangeRoute(String iface, int uid_start, int uid_end, boolean forward_dns);
/**
* Clears the special routing rules for users [uid_start,uid_end]
@@ -402,31 +380,6 @@ interface INetworkManagementService
void clearHostExemption(in LinkAddress host);
/**
- * Set a process (pid) to use the name servers associated with the specified interface.
- */
- void setDnsInterfaceForPid(String iface, int pid);
-
- /**
- * Clear a process (pid) from being associated with an interface.
- */
- void clearDnsInterfaceForPid(int pid);
-
- /**
- * Set a range of user ids to use the name servers associated with the specified interface.
- */
- void setDnsInterfaceForUidRange(String iface, int uid_start, int uid_end);
-
- /**
- * Clear a user range from being associated with an interface.
- */
- void clearDnsInterfaceForUidRange(String iface, int uid_start, int uid_end);
-
- /**
- * Clear the mappings from pid to Dns interface and from uid range to Dns interface.
- */
- void clearDnsInterfaceMaps();
-
- /**
* Start the clatd (464xlat) service
*/
void startClatd(String interfaceName);
@@ -455,4 +408,33 @@ interface INetworkManagementService
* Check whether the mobile radio is currently active.
*/
boolean isNetworkActive();
+
+ /**
+ * Setup a new network.
+ */
+ void createNetwork(int netId);
+
+ /**
+ * Remove a network.
+ */
+ void removeNetwork(int netId);
+
+ /**
+ * Add an interface to a network.
+ */
+ void addInterfaceToNetwork(String iface, int netId);
+
+ /**
+ * Remove an Interface from a network.
+ */
+ void removeInterfaceFromNetwork(String iface, int netId);
+
+ void addLegacyRouteForNetId(int netId, in RouteInfo routeInfo, int uid);
+ void removeLegacyRouteForNetId(int netId, in RouteInfo routeInfo, int uid);
+
+ void setDefaultNetId(int netId);
+ void clearDefaultNetId();
+
+ void setPermission(boolean internal, boolean changeNetState, in int[] uids);
+ void clearPermission(in int[] uids);
}
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 6c7b08d..61194e9 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -38,7 +38,7 @@ interface IPowerManager
void userActivity(long time, int event, int flags);
void wakeUp(long time);
- void goToSleep(long time, int reason);
+ void goToSleep(long time, int reason, int flags);
void nap(long time);
boolean isInteractive();
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index c2cd3be..c01f688 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -17,7 +17,14 @@
package android.os;
import android.util.ArrayMap;
-
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
import java.util.Set;
/**
@@ -25,14 +32,16 @@ import java.util.Set;
* restored.
*
*/
-public final class PersistableBundle extends CommonBundle {
+public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable,
+ XmlUtils.WriteMapCallback {
+ private static final String TAG_PERSISTABLEMAP = "pbundle_as_map";
public static final PersistableBundle EMPTY;
static final Parcel EMPTY_PARCEL;
static {
EMPTY = new PersistableBundle();
EMPTY.mMap = ArrayMap.EMPTY;
- EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL;
+ EMPTY_PARCEL = BaseBundle.EMPTY_PARCEL;
}
/**
@@ -43,31 +52,6 @@ public final class PersistableBundle extends CommonBundle {
}
/**
- * 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.
*
@@ -88,41 +72,50 @@ public final class PersistableBundle extends CommonBundle {
}
/**
- * Make a PersistableBundle for a single key/value pair.
+ * Constructs a PersistableBundle containing the mappings passed in.
*
- * @hide
+ * @param map a Map containing only those items that can be persisted.
+ * @throws IllegalArgumentException if any element of #map cannot be persisted.
*/
- public static PersistableBundle forPair(String key, String value) {
- PersistableBundle b = new PersistableBundle(1);
- b.putString(key, value);
- return b;
- }
+ private PersistableBundle(Map<String, Object> map) {
+ super();
- /**
- * @hide
- */
- @Override
- public String getPairValue() {
- return super.getPairValue();
+ // First stuff everything in.
+ putAll(map);
+
+ // Now verify each item throwing an exception if there is a violation.
+ Set<String> keys = map.keySet();
+ Iterator<String> iterator = keys.iterator();
+ while (iterator.hasNext()) {
+ String key = iterator.next();
+ Object value = map.get(key);
+ if (value instanceof Map) {
+ // Fix up any Maps by replacing them with PersistableBundles.
+ putPersistableBundle(key, new PersistableBundle((Map<String, Object>) value));
+ } else if (!(value instanceof Integer) && !(value instanceof Long) &&
+ !(value instanceof Double) && !(value instanceof String) &&
+ !(value instanceof int[]) && !(value instanceof long[]) &&
+ !(value instanceof double[]) && !(value instanceof String[]) &&
+ !(value instanceof PersistableBundle) && (value != null)) {
+ throw new IllegalArgumentException("Bad value in PersistableBundle key=" + key +
+ " value=" + value);
+ }
+ }
}
- /**
- * 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);
+ /* package */ PersistableBundle(Parcel parcelledData, int length) {
+ super(parcelledData, length);
}
/**
- * Return the ClassLoader currently associated with this PersistableBundle.
+ * Make a PersistableBundle for a single key/value pair.
+ *
+ * @hide
*/
- @Override
- public ClassLoader getClassLoader() {
- return super.getClassLoader();
+ public static PersistableBundle forPair(String key, String value) {
+ PersistableBundle b = new PersistableBundle(1);
+ b.putString(key, value);
+ return b;
}
/**
@@ -135,188 +128,6 @@ public final class PersistableBundle extends CommonBundle {
}
/**
- * @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.
*
@@ -324,109 +135,8 @@ public final class PersistableBundle extends CommonBundle {
* @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);
+ unparcel();
+ mMap.put(key, value);
}
/**
@@ -437,61 +147,18 @@ public final class PersistableBundle extends CommonBundle {
* @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);
+ 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;
+ }
}
public static final Parcelable.Creator<PersistableBundle> CREATOR =
@@ -507,6 +174,38 @@ public final class PersistableBundle extends CommonBundle {
}
};
+ /** @hide */
+ @Override
+ public void writeUnknownObject(Object v, String name, XmlSerializer out)
+ throws XmlPullParserException, IOException {
+ if (v instanceof PersistableBundle) {
+ out.startTag(null, TAG_PERSISTABLEMAP);
+ out.attribute(null, "name", name);
+ ((PersistableBundle) v).saveToXml(out);
+ out.endTag(null, TAG_PERSISTABLEMAP);
+ } else {
+ throw new XmlPullParserException("Unknown Object o=" + v);
+ }
+ }
+
+ /** @hide */
+ public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
+ unparcel();
+ XmlUtils.writeMapXml(mMap, out, this);
+ }
+
+ /** @hide */
+ static class MyReadMapCallback implements XmlUtils.ReadMapCallback {
+ @Override
+ public Object readThisUnknownObjectXml(XmlPullParser in, String tag)
+ throws XmlPullParserException, IOException {
+ if (TAG_PERSISTABLEMAP.equals(tag)) {
+ return restoreFromXml(in);
+ }
+ throw new XmlPullParserException("Unknown tag=" + tag);
+ }
+ }
+
/**
* Report the nature of this Parcelable's contents
*/
@@ -524,19 +223,27 @@ public final class PersistableBundle extends CommonBundle {
public void writeToParcel(Parcel parcel, int flags) {
final boolean oldAllowFds = parcel.pushAllowFds(false);
try {
- super.writeToParcelInner(parcel, flags);
+ 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);
+ /** @hide */
+ public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOException,
+ XmlPullParserException {
+ final int outerDepth = in.getDepth();
+ final String startTag = in.getName();
+ final String[] tagName = new String[1];
+ int event;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+ (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
+ if (event == XmlPullParser.START_TAG) {
+ return new PersistableBundle((Map<String, Object>)
+ XmlUtils.readThisMapXml(in, startTag, tagName, new MyReadMapCallback()));
+ }
+ }
+ return EMPTY;
}
@Override
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index f8d7c3e..d5177e8 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -321,6 +321,12 @@ public final class PowerManager {
*/
public static final String REBOOT_RECOVERY = "recovery";
+ /**
+ * Go to sleep flag: Skip dozing state and directly go to full sleep.
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_FLAG_NO_DOZE = 1 << 0;
+
final Context mContext;
final IPowerManager mService;
final Handler mHandler;
@@ -366,15 +372,6 @@ public final class PowerManager {
}
/**
- * Returns true if the screen auto-brightness adjustment setting should
- * be available in the UI. This setting is experimental and disabled by default.
- * @hide
- */
- public static boolean useScreenAutoBrightnessAdjustmentFeature() {
- return SystemProperties.getBoolean("persist.power.useautobrightadj", false);
- }
-
- /**
* Returns true if the twilight service should be used to adjust screen brightness
* policy. This setting is experimental and disabled by default.
* @hide
@@ -509,8 +506,15 @@ public final class PowerManager {
* @see #wakeUp
*/
public void goToSleep(long time) {
+ goToSleep(time, GO_TO_SLEEP_REASON_USER, 0);
+ }
+
+ /**
+ * @hide
+ */
+ public void goToSleep(long time, int reason, int flags) {
try {
- mService.goToSleep(time, GO_TO_SLEEP_REASON_USER);
+ mService.goToSleep(time, reason, flags);
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index cb3d528..69b828f 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -55,6 +55,14 @@ public abstract class PowerManagerInternal {
*/
public abstract void setUserActivityTimeoutOverrideFromWindowManager(long timeoutMillis);
+ public abstract boolean getLowPowerModeEnabled();
+
+ public interface LowPowerModeListener {
+ public void onLowPowerModeChanged(boolean enabled);
+ }
+
+ public abstract void registerLowPowerModeObserver(LowPowerModeListener listener);
+
// TODO: Remove this and retrieve as a local service instead.
public abstract void setPolicy(WindowManagerPolicy policy);
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 1b3aa0a..112ec1d 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -31,13 +31,17 @@ import java.util.Arrays;
import java.util.List;
/*package*/ class ZygoteStartFailedEx extends Exception {
- /**
- * Something prevented the zygote process startup from happening normally
- */
+ ZygoteStartFailedEx(String s) {
+ super(s);
+ }
- ZygoteStartFailedEx() {};
- ZygoteStartFailedEx(String s) {super(s);}
- ZygoteStartFailedEx(Throwable cause) {super(cause);}
+ ZygoteStartFailedEx(Throwable cause) {
+ super(cause);
+ }
+
+ ZygoteStartFailedEx(String s, Throwable cause) {
+ super(s, cause);
+ }
}
/**
@@ -46,9 +50,15 @@ import java.util.List;
public class Process {
private static final String LOG_TAG = "Process";
- private static final String ZYGOTE_SOCKET = "zygote";
+ /**
+ * @hide for internal use only.
+ */
+ public static final String ZYGOTE_SOCKET = "zygote";
- private static final String SECONDARY_ZYGOTE_SOCKET = "zygote_secondary";
+ /**
+ * @hide for internal use only.
+ */
+ public static final String SECONDARY_ZYGOTE_SOCKET = "zygote_secondary";
/**
* Defines the UID/GID under which system code runs.
@@ -338,8 +348,10 @@ public class Process {
/**
* State for communicating with the zygote process.
+ *
+ * @hide for internal use only.
*/
- static class ZygoteState {
+ public static class ZygoteState {
final LocalSocket socket;
final DataInputStream inputStream;
final BufferedWriter writer;
@@ -355,55 +367,26 @@ public class Process {
this.abiList = abiList;
}
- static ZygoteState connect(String socketAddress, int tries) throws ZygoteStartFailedEx {
- LocalSocket zygoteSocket = null;
+ public static ZygoteState connect(String socketAddress) throws IOException {
DataInputStream zygoteInputStream = null;
BufferedWriter zygoteWriter = null;
+ final LocalSocket zygoteSocket = new LocalSocket();
- /*
- * See bug #811181: Sometimes runtime can make it up before zygote.
- * Really, we'd like to do something better to avoid this condition,
- * but for now just wait a bit...
- *
- * TODO: This bug was filed in 2007. Get rid of this code. The zygote
- * forks the system_server so it shouldn't be possible for the zygote
- * socket to be brought up after the system_server is.
- */
- for (int i = 0; i < tries; i++) {
- if (i > 0) {
- try {
- Log.i(LOG_TAG, "Zygote not up yet, sleeping...");
- Thread.sleep(ZYGOTE_RETRY_MILLIS);
- } catch (InterruptedException ex) {
- throw new ZygoteStartFailedEx(ex);
- }
- }
+ try {
+ zygoteSocket.connect(new LocalSocketAddress(socketAddress,
+ LocalSocketAddress.Namespace.RESERVED));
- try {
- zygoteSocket = new LocalSocket();
- zygoteSocket.connect(new LocalSocketAddress(socketAddress,
- LocalSocketAddress.Namespace.RESERVED));
-
- zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
-
- zygoteWriter = new BufferedWriter(new OutputStreamWriter(
- zygoteSocket.getOutputStream()), 256);
- break;
- } catch (IOException ex) {
- if (zygoteSocket != null) {
- try {
- zygoteSocket.close();
- } catch (IOException ex2) {
- Log.e(LOG_TAG,"I/O exception on close after exception", ex2);
- }
- }
+ zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
- zygoteSocket = null;
+ zygoteWriter = new BufferedWriter(new OutputStreamWriter(
+ zygoteSocket.getOutputStream()), 256);
+ } catch (IOException ex) {
+ try {
+ zygoteSocket.close();
+ } catch (IOException ignore) {
}
- }
- if (zygoteSocket == null) {
- throw new ZygoteStartFailedEx("connect failed");
+ throw ex;
}
String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
@@ -417,7 +400,7 @@ public class Process {
return abiList.contains(abi);
}
- void close() {
+ public void close() {
try {
socket.close();
} catch (IOException ex) {
@@ -503,27 +486,22 @@ public class Process {
* @throws ZygoteStartFailedEx if the query failed.
*/
private static String getAbiList(BufferedWriter writer, DataInputStream inputStream)
- throws ZygoteStartFailedEx {
- try {
-
- // Each query starts with the argument count (1 in this case)
- writer.write("1");
- // ... followed by a new-line.
- writer.newLine();
- // ... followed by our only argument.
- writer.write("--query-abi-list");
- writer.newLine();
- writer.flush();
-
- // The response is a length prefixed stream of ASCII bytes.
- int numBytes = inputStream.readInt();
- byte[] bytes = new byte[numBytes];
- inputStream.readFully(bytes);
-
- return new String(bytes, StandardCharsets.US_ASCII);
- } catch (IOException ioe) {
- throw new ZygoteStartFailedEx(ioe);
- }
+ throws IOException {
+ // Each query starts with the argument count (1 in this case)
+ writer.write("1");
+ // ... followed by a new-line.
+ writer.newLine();
+ // ... followed by our only argument.
+ writer.write("--query-abi-list");
+ writer.newLine();
+ writer.flush();
+
+ // The response is a length prefixed stream of ASCII bytes.
+ int numBytes = inputStream.readInt();
+ byte[] bytes = new byte[numBytes];
+ inputStream.readFully(bytes);
+
+ return new String(bytes, StandardCharsets.US_ASCII);
}
/**
@@ -677,30 +655,16 @@ public class Process {
}
/**
- * Returns the number of times we attempt a connection to the zygote. We
- * sleep for {@link #ZYGOTE_RETRY_MILLIS} milliseconds between each try.
- *
- * This could probably be removed, see TODO in {@code ZygoteState#connect}.
- */
- private static int getNumTries(ZygoteState state) {
- // Retry 10 times for the first connection to each zygote.
- if (state == null) {
- return 11;
- }
-
- // This means the connection has already been established, but subsequently
- // closed, possibly due to an IOException. We retry just once if that's the
- // case.
- return 1;
- }
-
- /**
* Tries to open socket to Zygote process if not already open. If
* already open, does nothing. May block and retry.
*/
private static ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
- primaryZygoteState = ZygoteState.connect(ZYGOTE_SOCKET, getNumTries(primaryZygoteState));
+ try {
+ primaryZygoteState = ZygoteState.connect(ZYGOTE_SOCKET);
+ } catch (IOException ioe) {
+ throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
+ }
}
if (primaryZygoteState.matches(abi)) {
@@ -709,8 +673,11 @@ public class Process {
// The primary zygote didn't match. Try the secondary.
if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
- secondaryZygoteState = ZygoteState.connect(SECONDARY_ZYGOTE_SOCKET,
- getNumTries(secondaryZygoteState));
+ try {
+ secondaryZygoteState = ZygoteState.connect(SECONDARY_ZYGOTE_SOCKET);
+ } catch (IOException ioe) {
+ throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
+ }
}
if (secondaryZygoteState.matches(abi)) {
diff --git a/core/java/android/os/RemoteException.java b/core/java/android/os/RemoteException.java
index e30d24f..98d7523 100644
--- a/core/java/android/os/RemoteException.java
+++ b/core/java/android/os/RemoteException.java
@@ -28,4 +28,9 @@ public class RemoteException extends AndroidException {
public RemoteException(String message) {
super(message);
}
+
+ /** {@hide} */
+ public RuntimeException rethrowAsRuntimeException() {
+ throw new RuntimeException(this);
+ }
}
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 57ed979..474192f 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -70,6 +70,8 @@ public final class Trace {
public static final long TRACE_TAG_DALVIK = 1L << 14;
/** @hide */
public static final long TRACE_TAG_RS = 1L << 15;
+ /** @hide */
+ public static final long TRACE_TAG_BIONIC = 1L << 16;
private static final long TRACE_TAG_NOT_READY = 1L << 63;
private static final int MAX_SECTION_NAME_LEN = 127;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 312cdbe..ee219e3 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -266,6 +266,17 @@ public class UserManager {
*/
public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
+ /**
+ * Key for user restrictions. Specifies that the user is not allowed to send or receive
+ * phone calls or text messages. Emergency calls may still be permitted.
+ * The default value is <code>false</code>.
+ * <p/>
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_TELEPHONY = "no_telephony";
+
/** @hide */
public static final int PIN_VERIFICATION_FAILED_INCORRECT = -3;
/** @hide */
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 4963991..68b91cb 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -58,24 +58,6 @@ 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;
@@ -663,4 +645,14 @@ public class StorageManager {
return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
DEFAULT_FULL_THRESHOLD_BYTES);
}
+
+ /// Consts to match the password types in cryptfs.h
+ /** @hide */
+ public static final int CRYPT_TYPE_PASSWORD = 0;
+ /** @hide */
+ public static final int CRYPT_TYPE_DEFAULT = 1;
+ /** @hide */
+ public static final int CRYPT_TYPE_PATTERN = 2;
+ /** @hide */
+ public static final int CRYPT_TYPE_PIN = 3;
}
diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java
index 5c8c8e9..ad940c6 100644
--- a/core/java/android/preference/PreferenceManager.java
+++ b/core/java/android/preference/PreferenceManager.java
@@ -621,8 +621,9 @@ public class PreferenceManager {
* Registers a listener.
*
* @see OnActivityStopListener
+ * @hide
*/
- void registerOnActivityStopListener(OnActivityStopListener listener) {
+ public void registerOnActivityStopListener(OnActivityStopListener listener) {
synchronized (this) {
if (mActivityStopListeners == null) {
mActivityStopListeners = new ArrayList<OnActivityStopListener>();
@@ -638,8 +639,9 @@ public class PreferenceManager {
* Unregisters a listener.
*
* @see OnActivityStopListener
+ * @hide
*/
- void unregisterOnActivityStopListener(OnActivityStopListener listener) {
+ public void unregisterOnActivityStopListener(OnActivityStopListener listener) {
synchronized (this) {
if (mActivityStopListeners != null) {
mActivityStopListeners.remove(listener);
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
new file mode 100644
index 0000000..d66fc0f
--- /dev/null
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -0,0 +1,318 @@
+/*
+ * 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.preference;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.preference.VolumePreference.VolumeStore;
+import android.provider.Settings;
+import android.provider.Settings.System;
+import android.util.Log;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+/**
+ * Turns a {@link SeekBar} into a volume control.
+ * @hide
+ */
+public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callback {
+ private static final String TAG = "SeekBarVolumizer";
+
+ public interface Callback {
+ void onSampleStarting(SeekBarVolumizer sbv);
+ }
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final H mUiHandler = new H();
+ private final Callback mCallback;
+ private final Uri mDefaultUri;
+ private final AudioManager mAudioManager;
+ private final int mStreamType;
+ private final int mMaxStreamVolume;
+ private final Receiver mReceiver = new Receiver();
+ private final Observer mVolumeObserver;
+
+ private int mOriginalStreamVolume;
+ private Ringtone mRingtone;
+ private int mLastProgress = -1;
+ private SeekBar mSeekBar;
+ private int mVolumeBeforeMute = -1;
+
+ private static final int MSG_SET_STREAM_VOLUME = 0;
+ private static final int MSG_START_SAMPLE = 1;
+ private static final int MSG_STOP_SAMPLE = 2;
+ private static final int MSG_INIT_SAMPLE = 3;
+ private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
+
+ public SeekBarVolumizer(Context context, int streamType, Uri defaultUri,
+ Callback callback) {
+ mContext = context;
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mStreamType = streamType;
+ mMaxStreamVolume = mAudioManager.getStreamMaxVolume(mStreamType);
+ HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
+ thread.start();
+ mHandler = new Handler(thread.getLooper(), this);
+ mCallback = callback;
+ mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
+ mVolumeObserver = new Observer(mHandler);
+ mContext.getContentResolver().registerContentObserver(
+ System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
+ false, mVolumeObserver);
+ mReceiver.setListening(true);
+ if (defaultUri == null) {
+ if (mStreamType == AudioManager.STREAM_RING) {
+ defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
+ } else if (mStreamType == AudioManager.STREAM_NOTIFICATION) {
+ defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI;
+ } else {
+ defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
+ }
+ }
+ mDefaultUri = defaultUri;
+ mHandler.sendEmptyMessage(MSG_INIT_SAMPLE);
+ }
+
+ public void setSeekBar(SeekBar seekBar) {
+ if (mSeekBar != null) {
+ mSeekBar.setOnSeekBarChangeListener(null);
+ }
+ mSeekBar = seekBar;
+ mSeekBar.setOnSeekBarChangeListener(null);
+ mSeekBar.setMax(mMaxStreamVolume);
+ mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume);
+ mSeekBar.setOnSeekBarChangeListener(this);
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SET_STREAM_VOLUME:
+ mAudioManager.setStreamVolume(mStreamType, mLastProgress, 0);
+ break;
+ case MSG_START_SAMPLE:
+ onStartSample();
+ break;
+ case MSG_STOP_SAMPLE:
+ onStopSample();
+ break;
+ case MSG_INIT_SAMPLE:
+ onInitSample();
+ break;
+ default:
+ Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
+ }
+ return true;
+ }
+
+ private void onInitSample() {
+ mRingtone = RingtoneManager.getRingtone(mContext, mDefaultUri);
+ if (mRingtone != null) {
+ mRingtone.setStreamType(mStreamType);
+ }
+ }
+
+ private void postStartSample() {
+ mHandler.removeMessages(MSG_START_SAMPLE);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
+ isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
+ }
+
+ private void onStartSample() {
+ if (!isSamplePlaying()) {
+ if (mCallback != null) {
+ mCallback.onSampleStarting(this);
+ }
+ if (mRingtone != null) {
+ try {
+ mRingtone.play();
+ } catch (Throwable e) {
+ Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e);
+ }
+ }
+ }
+ }
+
+ void postStopSample() {
+ // remove pending delayed start messages
+ mHandler.removeMessages(MSG_START_SAMPLE);
+ mHandler.removeMessages(MSG_STOP_SAMPLE);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_SAMPLE));
+ }
+
+ private void onStopSample() {
+ if (mRingtone != null) {
+ mRingtone.stop();
+ }
+ }
+
+ public void stop() {
+ postStopSample();
+ mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
+ mSeekBar.setOnSeekBarChangeListener(null);
+ mReceiver.setListening(false);
+ mHandler.getLooper().quitSafely();
+ }
+
+ public void revertVolume() {
+ mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
+ }
+
+ public void onProgressChanged(SeekBar seekBar, int progress,
+ boolean fromTouch) {
+ if (!fromTouch) {
+ return;
+ }
+
+ postSetVolume(progress);
+ }
+
+ void postSetVolume(int progress) {
+ // Do the volume changing separately to give responsive UI
+ mLastProgress = progress;
+ mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
+ }
+
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ postStartSample();
+ }
+
+ public boolean isSamplePlaying() {
+ return mRingtone != null && mRingtone.isPlaying();
+ }
+
+ public void startSample() {
+ postStartSample();
+ }
+
+ public void stopSample() {
+ postStopSample();
+ }
+
+ public SeekBar getSeekBar() {
+ return mSeekBar;
+ }
+
+ public void changeVolumeBy(int amount) {
+ mSeekBar.incrementProgressBy(amount);
+ postSetVolume(mSeekBar.getProgress());
+ postStartSample();
+ mVolumeBeforeMute = -1;
+ }
+
+ public void muteVolume() {
+ if (mVolumeBeforeMute != -1) {
+ mSeekBar.setProgress(mVolumeBeforeMute);
+ postSetVolume(mVolumeBeforeMute);
+ postStartSample();
+ mVolumeBeforeMute = -1;
+ } else {
+ mVolumeBeforeMute = mSeekBar.getProgress();
+ mSeekBar.setProgress(0);
+ postStopSample();
+ postSetVolume(0);
+ }
+ }
+
+ public void onSaveInstanceState(VolumeStore volumeStore) {
+ if (mLastProgress >= 0) {
+ volumeStore.volume = mLastProgress;
+ volumeStore.originalVolume = mOriginalStreamVolume;
+ }
+ }
+
+ public void onRestoreInstanceState(VolumeStore volumeStore) {
+ if (volumeStore.volume != -1) {
+ mOriginalStreamVolume = volumeStore.originalVolume;
+ mLastProgress = volumeStore.volume;
+ postSetVolume(mLastProgress);
+ }
+ }
+
+ private final class H extends Handler {
+ private static final int UPDATE_SLIDER = 1;
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == UPDATE_SLIDER) {
+ if (mSeekBar != null) {
+ mSeekBar.setProgress(msg.arg1);
+ mLastProgress = mSeekBar.getProgress();
+ }
+ }
+ }
+
+ public void postUpdateSlider(int volume) {
+ obtainMessage(UPDATE_SLIDER, volume, 0).sendToTarget();
+ }
+ }
+
+ private final class Observer extends ContentObserver {
+ public Observer(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ if (mSeekBar != null && mAudioManager != null) {
+ final int volume = mAudioManager.getStreamVolume(mStreamType);
+ mUiHandler.postUpdateSlider(volume);
+ }
+ }
+ }
+
+ private final class Receiver extends BroadcastReceiver {
+ private boolean mListening;
+
+ public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+ if (listening) {
+ final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
+ mContext.registerReceiver(this, filter);
+ } else {
+ mContext.unregisterReceiver(this);
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!AudioManager.VOLUME_CHANGED_ACTION.equals(intent.getAction())) return;
+ final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ final int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
+ if (mSeekBar != null && streamType == mStreamType && streamValue != -1) {
+ mUiHandler.postUpdateSlider(streamValue);
+ }
+ }
+ }
+}
diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java
index 29f2545..df9e10e 100644
--- a/core/java/android/preference/VolumePreference.java
+++ b/core/java/android/preference/VolumePreference.java
@@ -19,32 +19,20 @@ package android.preference;
import android.app.Dialog;
import android.content.Context;
import android.content.res.TypedArray;
-import android.database.ContentObserver;
-import android.media.AudioManager;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
-import android.provider.Settings;
-import android.provider.Settings.System;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.SeekBar;
-import android.widget.SeekBar.OnSeekBarChangeListener;
/**
* @hide
*/
public class VolumePreference extends SeekBarDialogPreference implements
- PreferenceManager.OnActivityStopListener, View.OnKeyListener {
+ PreferenceManager.OnActivityStopListener, View.OnKeyListener, SeekBarVolumizer.Callback {
- private static final String TAG = "VolumePreference";
+ static final String TAG = "VolumePreference";
private int mStreamType;
@@ -66,7 +54,7 @@ public class VolumePreference extends SeekBarDialogPreference implements
}
public VolumePreference(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
+ this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle);
}
public void setStreamType(int streamType) {
@@ -78,7 +66,8 @@ public class VolumePreference extends SeekBarDialogPreference implements
super.onBindDialogView(view);
final SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
- mSeekBarVolumizer = new SeekBarVolumizer(getContext(), seekBar, mStreamType);
+ mSeekBarVolumizer = new SeekBarVolumizer(getContext(), mStreamType, null, this);
+ mSeekBarVolumizer.setSeekBar(seekBar);
getPreferenceManager().registerOnActivityStopListener(this);
@@ -152,7 +141,8 @@ public class VolumePreference extends SeekBarDialogPreference implements
}
- protected void onSampleStarting(SeekBarVolumizer volumizer) {
+ @Override
+ public void onSampleStarting(SeekBarVolumizer volumizer) {
if (mSeekBarVolumizer != null && volumizer != mSeekBarVolumizer) {
mSeekBarVolumizer.stopSample();
}
@@ -228,213 +218,4 @@ public class VolumePreference extends SeekBarDialogPreference implements
}
};
}
-
- /**
- * Turns a {@link SeekBar} into a volume control.
- */
- public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callback {
-
- private Context mContext;
- private Handler mHandler;
-
- private AudioManager mAudioManager;
- private int mStreamType;
- private int mOriginalStreamVolume;
- private Ringtone mRingtone;
-
- private int mLastProgress = -1;
- private SeekBar mSeekBar;
- private int mVolumeBeforeMute = -1;
-
- private static final int MSG_SET_STREAM_VOLUME = 0;
- private static final int MSG_START_SAMPLE = 1;
- private static final int MSG_STOP_SAMPLE = 2;
- private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
-
- private ContentObserver mVolumeObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
- if (mSeekBar != null && mAudioManager != null) {
- int volume = mAudioManager.getStreamVolume(mStreamType);
- mSeekBar.setProgress(volume);
- }
- }
- };
-
- public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType) {
- this(context, seekBar, streamType, null);
- }
-
- public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType, Uri defaultUri) {
- mContext = context;
- mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- mStreamType = streamType;
- mSeekBar = seekBar;
-
- HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
- thread.start();
- mHandler = new Handler(thread.getLooper(), this);
-
- initSeekBar(seekBar, defaultUri);
- }
-
- private void initSeekBar(SeekBar seekBar, Uri defaultUri) {
- seekBar.setMax(mAudioManager.getStreamMaxVolume(mStreamType));
- mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
- seekBar.setProgress(mOriginalStreamVolume);
- seekBar.setOnSeekBarChangeListener(this);
-
- mContext.getContentResolver().registerContentObserver(
- System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
- false, mVolumeObserver);
-
- if (defaultUri == null) {
- if (mStreamType == AudioManager.STREAM_RING) {
- defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
- } else if (mStreamType == AudioManager.STREAM_NOTIFICATION) {
- defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI;
- } else {
- defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
- }
- }
-
- mRingtone = RingtoneManager.getRingtone(mContext, defaultUri);
-
- if (mRingtone != null) {
- mRingtone.setStreamType(mStreamType);
- }
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_SET_STREAM_VOLUME:
- mAudioManager.setStreamVolume(mStreamType, mLastProgress, 0);
- break;
- case MSG_START_SAMPLE:
- onStartSample();
- break;
- case MSG_STOP_SAMPLE:
- onStopSample();
- break;
- default:
- Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
- }
- return true;
- }
-
- private void postStartSample() {
- mHandler.removeMessages(MSG_START_SAMPLE);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
- isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
- }
-
- private void onStartSample() {
- if (!isSamplePlaying()) {
- onSampleStarting(this);
- if (mRingtone != null) {
- mRingtone.play();
- }
- }
- }
-
- private void postStopSample() {
- // remove pending delayed start messages
- mHandler.removeMessages(MSG_START_SAMPLE);
- mHandler.removeMessages(MSG_STOP_SAMPLE);
- mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_SAMPLE));
- }
-
- private void onStopSample() {
- if (mRingtone != null) {
- mRingtone.stop();
- }
- }
-
- public void stop() {
- postStopSample();
- mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
- mSeekBar.setOnSeekBarChangeListener(null);
- }
-
- public void revertVolume() {
- mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
- }
-
- public void onProgressChanged(SeekBar seekBar, int progress,
- boolean fromTouch) {
- if (!fromTouch) {
- return;
- }
-
- postSetVolume(progress);
- }
-
- void postSetVolume(int progress) {
- // Do the volume changing separately to give responsive UI
- mLastProgress = progress;
- mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
- mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
- }
-
- public void onStartTrackingTouch(SeekBar seekBar) {
- }
-
- public void onStopTrackingTouch(SeekBar seekBar) {
- postStartSample();
- }
-
- public boolean isSamplePlaying() {
- return mRingtone != null && mRingtone.isPlaying();
- }
-
- public void startSample() {
- postStartSample();
- }
-
- public void stopSample() {
- postStopSample();
- }
-
- public SeekBar getSeekBar() {
- return mSeekBar;
- }
-
- public void changeVolumeBy(int amount) {
- mSeekBar.incrementProgressBy(amount);
- postSetVolume(mSeekBar.getProgress());
- postStartSample();
- mVolumeBeforeMute = -1;
- }
-
- public void muteVolume() {
- if (mVolumeBeforeMute != -1) {
- mSeekBar.setProgress(mVolumeBeforeMute);
- postSetVolume(mVolumeBeforeMute);
- postStartSample();
- mVolumeBeforeMute = -1;
- } else {
- mVolumeBeforeMute = mSeekBar.getProgress();
- mSeekBar.setProgress(0);
- postStopSample();
- postSetVolume(0);
- }
- }
-
- public void onSaveInstanceState(VolumeStore volumeStore) {
- if (mLastProgress >= 0) {
- volumeStore.volume = mLastProgress;
- volumeStore.originalVolume = mOriginalStreamVolume;
- }
- }
-
- public void onRestoreInstanceState(VolumeStore volumeStore) {
- if (volumeStore.volume != -1) {
- mOriginalStreamVolume = volumeStore.originalVolume;
- mLastProgress = volumeStore.volume;
- postSetVolume(mLastProgress);
- }
- }
- }
}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 11678a6..6db78f4 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -903,8 +903,6 @@ public final class ContactsContract {
/**
* 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";
@@ -1154,8 +1152,6 @@ public final class ContactsContract {
* address book index, which is usually the first letter of the sort key.
* When this parameter is supplied, the row counts are returned in the
* cursor extras bundle.
- *
- * @hide
*/
public final static class ContactCounts {
@@ -1165,7 +1161,24 @@ public final class ContactsContract {
* first letter of the sort key. This parameter does not affect the main
* content of the cursor.
*
- * @hide
+ * <p>
+ * <pre>
+ * Example:
+ * Uri uri = Contacts.CONTENT_URI.buildUpon()
+ * .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true")
+ * .build();
+ * Cursor cursor = getContentResolver().query(uri,
+ * new String[] {Contacts.DISPLAY_NAME},
+ * null, null, null);
+ * Bundle bundle = cursor.getExtras();
+ * if (bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES) &&
+ * bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS)) {
+ * String sections[] =
+ * bundle.getStringArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
+ * int counts[] = bundle.getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
+ * }
+ * </pre>
+ * </p>
*/
public static final String ADDRESS_BOOK_INDEX_EXTRAS = "address_book_index_extras";
@@ -1173,8 +1186,6 @@ public final class ContactsContract {
* The array of address book index titles, which are returned in the
* same order as the data in the cursor.
* <p>TYPE: String[]</p>
- *
- * @hide
*/
public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "address_book_index_titles";
@@ -1182,8 +1193,6 @@ public final class ContactsContract {
* The array of group counts for the corresponding group. Contains the same number
* of elements as the EXTRA_ADDRESS_BOOK_INDEX_TITLES array.
* <p>TYPE: int[]</p>
- *
- * @hide
*/
public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "address_book_index_counts";
}
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index b907375..6b8e2de 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -287,6 +287,16 @@ public final class DocumentsContract {
public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5;
/**
+ * Flag indicating that a document can be renamed.
+ *
+ * @see #COLUMN_FLAGS
+ * @see DocumentsContract#renameDocument(ContentProviderClient, Uri,
+ * String)
+ * @see DocumentsProvider#renameDocument(String, String)
+ */
+ public static final int FLAG_SUPPORTS_RENAME = 1 << 6;
+
+ /**
* Flag indicating that document titles should be hidden when viewing
* this directory in a larger format grid. For example, a directory
* containing only images may want the image thumbnails to speak for
@@ -494,6 +504,8 @@ public final class DocumentsContract {
/** {@hide} */
public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
/** {@hide} */
+ public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument";
+ /** {@hide} */
public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
/** {@hide} */
@@ -898,6 +910,45 @@ public final class DocumentsContract {
}
/**
+ * Change the display name of an existing document.
+ * <p>
+ * If the underlying provider needs to create a new
+ * {@link Document#COLUMN_DOCUMENT_ID} to represent the updated display
+ * name, that new document is returned and the original document is no
+ * longer valid. Otherwise, the original document is returned.
+ *
+ * @param documentUri document with {@link Document#FLAG_SUPPORTS_RENAME}
+ * @param displayName updated name for document
+ * @return the existing or new document after the rename, or {@code null} if
+ * failed.
+ */
+ public static Uri renameDocument(ContentResolver resolver, Uri documentUri,
+ String displayName) {
+ final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
+ documentUri.getAuthority());
+ try {
+ return renameDocument(client, documentUri, displayName);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to rename document", e);
+ return null;
+ } finally {
+ ContentProviderClient.releaseQuietly(client);
+ }
+ }
+
+ /** {@hide} */
+ public static Uri renameDocument(ContentProviderClient client, Uri documentUri,
+ String displayName) throws RemoteException {
+ final Bundle in = new Bundle();
+ in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
+ in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
+
+ final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in);
+ final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI);
+ return (outUri != null) ? outUri : documentUri;
+ }
+
+ /**
* Delete the given document.
*
* @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 1a7a00f2..066b4aa 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -19,9 +19,11 @@ package android.provider;
import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE;
import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
+import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
import static android.provider.DocumentsContract.getDocumentId;
import static android.provider.DocumentsContract.getRootId;
import static android.provider.DocumentsContract.getSearchDocumentsQuery;
+import static android.provider.DocumentsContract.isViaUri;
import android.content.ContentProvider;
import android.content.ContentResolver;
@@ -206,7 +208,7 @@ public abstract class DocumentsProvider extends ContentProvider {
* If the MIME type is not supported, the provider must throw.
* @param displayName the display name of the new document. The provider may
* alter this name to meet any internal constraints, such as
- * conflicting names.
+ * avoiding conflicting names.
*/
@SuppressWarnings("unused")
public String createDocument(String parentDocumentId, String mimeType, String displayName)
@@ -215,11 +217,33 @@ public abstract class DocumentsProvider extends ContentProvider {
}
/**
- * Delete the requested document. Upon returning, any URI permission grants
- * 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)}.
+ * Rename an existing document.
+ * <p>
+ * If a different {@link Document#COLUMN_DOCUMENT_ID} must be used to
+ * represent the renamed document, generate and return it. Any outstanding
+ * URI permission grants will be updated to point at the new document. If
+ * the original {@link Document#COLUMN_DOCUMENT_ID} is still valid after the
+ * rename, return {@code null}.
+ *
+ * @param documentId the document to rename.
+ * @param displayName the updated display name of the document. The provider
+ * may alter this name to meet any internal constraints, such as
+ * avoiding conflicting names.
+ */
+ @SuppressWarnings("unused")
+ public String renameDocument(String documentId, String displayName)
+ throws FileNotFoundException {
+ throw new UnsupportedOperationException("Rename not supported");
+ }
+
+ /**
+ * Delete the requested document.
+ * <p>
+ * Upon returning, any URI permission grants 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.
*/
@@ -523,26 +547,33 @@ public abstract class DocumentsProvider extends ContentProvider {
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;
- }
+ // the narrow URI.
+ final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri);
context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags);
return narrowUri;
}
return null;
}
+ private static int getCallingOrSelfUriPermissionModeFlags(Context context, Uri uri) {
+ // TODO: move this to a direct AMS call
+ int modeFlags = 0;
+ if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ == PackageManager.PERMISSION_GRANTED) {
+ 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;
+ }
+ return modeFlags;
+ }
+
/**
* Implementation is provided by the parent class. Throws by default, and
* cannot be overriden.
@@ -588,6 +619,7 @@ public abstract class DocumentsProvider extends ContentProvider {
return super.call(method, arg, extras);
}
+ final Context context = getContext();
final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
final String authority = documentUri.getAuthority();
final String documentId = DocumentsContract.getDocumentId(documentUri);
@@ -605,7 +637,6 @@ public abstract class DocumentsProvider extends ContentProvider {
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);
// No need to issue new grants here, since caller either has
@@ -615,6 +646,30 @@ public abstract class DocumentsProvider extends ContentProvider {
newDocumentId);
out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+ } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
+ enforceWritePermissionInner(documentUri);
+
+ final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
+ final String newDocumentId = renameDocument(documentId, displayName);
+
+ if (newDocumentId != null) {
+ final Uri newDocumentUri = DocumentsContract.buildDocumentMaybeViaUri(
+ documentUri, newDocumentId);
+
+ // If caller came in with a narrow grant, issue them a
+ // narrow grant for the newly renamed document.
+ if (!isViaUri(newDocumentUri)) {
+ final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
+ documentUri);
+ context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
+ }
+
+ out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+
+ // Original document no longer exists, clean up any grants
+ revokeDocumentPermission(documentId);
+ }
+
} else if (METHOD_DELETE_DOCUMENT.equals(method)) {
enforceWritePermissionInner(documentUri);
deleteDocument(documentId);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1847b55..bec401e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -769,6 +769,28 @@ public final class Settings {
public static final String
ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
+ /**
+ * Activity Action: Show Device Name Settings.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard
+ * against this.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String DEVICE_NAME_SETTINGS = "android.settings.DEVICE_NAME";
+
+ /**
+ * Activity Action: Show pairing settings.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard
+ * against this.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PAIRING_SETTINGS = "android.settings.PAIRING_SETTINGS";
+
// End of Intent actions for Settings
/**
@@ -1066,6 +1088,9 @@ public final class Settings {
MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_PING_COUNT);
MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_PING_DELAY_MS);
MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS);
+
+ // At one time in System, then Global, but now back in Secure
+ MOVED_TO_SECURE.add(Secure.INSTALL_NON_MARKET_APPS);
}
private static final HashSet<String> MOVED_TO_GLOBAL;
@@ -1080,7 +1105,6 @@ public final class Settings {
MOVED_TO_SECURE_THEN_GLOBAL.add(Global.BLUETOOTH_ON);
MOVED_TO_SECURE_THEN_GLOBAL.add(Global.DATA_ROAMING);
MOVED_TO_SECURE_THEN_GLOBAL.add(Global.DEVICE_PROVISIONED);
- MOVED_TO_SECURE_THEN_GLOBAL.add(Global.INSTALL_NON_MARKET_APPS);
MOVED_TO_SECURE_THEN_GLOBAL.add(Global.USB_MASS_STORAGE_ENABLED);
MOVED_TO_SECURE_THEN_GLOBAL.add(Global.HTTP_PROXY);
@@ -2503,6 +2527,14 @@ public final class Settings {
NOTIFICATION_SOUND
};
+ /**
+ * When to use Wi-Fi calling
+ *
+ * @see android.telephony.TelephonyManager.WifiCallingChoices
+ * @hide
+ */
+ public static final String WHEN_TO_MAKE_WIFI_CALLS = "when_to_make_wifi_calls";
+
// Settings moved to Settings.Secure
/**
@@ -2543,10 +2575,10 @@ public final class Settings {
public static final String HTTP_PROXY = Global.HTTP_PROXY;
/**
- * @deprecated Use {@link android.provider.Settings.Global#INSTALL_NON_MARKET_APPS} instead
+ * @deprecated Use {@link android.provider.Settings.Secure#INSTALL_NON_MARKET_APPS} instead
*/
@Deprecated
- public static final String INSTALL_NON_MARKET_APPS = Global.INSTALL_NON_MARKET_APPS;
+ public static final String INSTALL_NON_MARKET_APPS = Secure.INSTALL_NON_MARKET_APPS;
/**
* @deprecated Use {@link android.provider.Settings.Secure#LOCATION_PROVIDERS_ALLOWED}
@@ -2784,7 +2816,6 @@ public final class Settings {
MOVED_TO_GLOBAL.add(Settings.Global.DISPLAY_SIZE_FORCED);
MOVED_TO_GLOBAL.add(Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE);
MOVED_TO_GLOBAL.add(Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
- MOVED_TO_GLOBAL.add(Settings.Global.INSTALL_NON_MARKET_APPS);
MOVED_TO_GLOBAL.add(Settings.Global.MOBILE_DATA);
MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_DEV_BUCKET_DURATION);
MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_DEV_DELETE_AGE);
@@ -2881,6 +2912,7 @@ public final class Settings {
MOVED_TO_GLOBAL.add(Settings.Global.SET_GLOBAL_HTTP_PROXY);
MOVED_TO_GLOBAL.add(Settings.Global.DEFAULT_DNS_SERVER);
MOVED_TO_GLOBAL.add(Settings.Global.PREFERRED_NETWORK_MODE);
+ MOVED_TO_GLOBAL.add(Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY);
}
/** @hide */
@@ -3373,10 +3405,13 @@ public final class Settings {
public static final String HTTP_PROXY = Global.HTTP_PROXY;
/**
- * @deprecated Use {@link android.provider.Settings.Global#INSTALL_NON_MARKET_APPS} instead
+ * Whether applications can be installed for this user via the system's
+ * {@link Intent#ACTION_INSTALL_PACKAGE} mechanism.
+ *
+ * <p>1 = permit app installation via the system package installer intent
+ * <p>0 = do not allow use of the package installer
*/
- @Deprecated
- public static final String INSTALL_NON_MARKET_APPS = Global.INSTALL_NON_MARKET_APPS;
+ public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
/**
* Comma-separated list of location providers that activities may access. Do not rely on
@@ -3442,11 +3477,6 @@ 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)
*
@@ -3838,22 +3868,11 @@ public final class Settings {
/**
* 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.
*
@@ -3881,44 +3900,6 @@ public final class Settings {
"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
*/
@@ -4480,6 +4461,12 @@ public final class Settings {
INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF;
/**
+ * Whether the device should wake when the wake gesture sensor detects motion.
+ * @hide
+ */
+ public static final String WAKE_GESTURE_ENABLED = "wake_gesture_enabled";
+
+ /**
* The current night mode that has been selected by the user. Owned
* and controlled by UiModeManagerService. Constants are as per
* UiModeManager.
@@ -4574,6 +4561,12 @@ public final class Settings {
public static final String PAYMENT_SERVICE_SEARCH_URI = "payment_service_search_uri";
/**
+ * If enabled, intercepted notifications will be displayed (not suppressed) in zen mode.
+ * @hide
+ */
+ public static final String DISPLAY_INTERCEPTED_NOTIFICATIONS = "display_intercepted_notifications";
+
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
@@ -5078,13 +5071,10 @@ public final class Settings {
"download_manager_recommended_max_bytes_over_mobile";
/**
- * Whether the package installer should allow installation of apps downloaded from
- * sources other than Google Play.
- *
- * 1 = allow installing from other sources
- * 0 = only allow installing from Google Play
+ * @deprecated Use {@link android.provider.Settings.Secure#INSTALL_NON_MARKET_APPS} instead
*/
- public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
+ @Deprecated
+ public static final String INSTALL_NON_MARKET_APPS = Secure.INSTALL_NON_MARKET_APPS;
/**
* Whether mobile data connections are allowed by the user. See
@@ -5360,6 +5350,13 @@ public final class Settings {
*/
public static final String USE_GOOGLE_MAIL = "use_google_mail";
+ /**
+ * Webview Data reduction proxy key.
+ * @hide
+ */
+ public static final String WEBVIEW_DATA_REDUCTION_PROXY_KEY =
+ "webview_data_reduction_proxy_key";
+
/**
* Whether Wifi display is enabled/disabled
* 0=disabled. 1=enabled.
@@ -6196,6 +6193,13 @@ public final class Settings {
/** @hide */ public static final int HEADS_UP_ON = 1;
/**
+ * The name of the device
+ *
+ * @hide
+ */
+ public static final String DEVICE_NAME = "device_name";
+
+ /**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
*
@@ -6236,6 +6240,13 @@ public final class Settings {
CALL_METHOD_GET_GLOBAL,
CALL_METHOD_PUT_GLOBAL);
+ // Certain settings have been moved from global to the per-user secure namespace
+ private static final HashSet<String> MOVED_TO_SECURE;
+ static {
+ MOVED_TO_SECURE = new HashSet<String>(1);
+ MOVED_TO_SECURE.add(Settings.Global.INSTALL_NON_MARKET_APPS);
+ }
+
/**
* Look up a name in the database.
* @param resolver to access the database with
@@ -6249,6 +6260,11 @@ public final class Settings {
/** @hide */
public static String getStringForUser(ContentResolver resolver, String name,
int userHandle) {
+ if (MOVED_TO_SECURE.contains(name)) {
+ Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global"
+ + " to android.provider.Settings.Secure, returning read-only value.");
+ return Secure.getStringForUser(resolver, name, userHandle);
+ }
return sNameValueCache.getStringForUser(resolver, name, userHandle);
}
@@ -6271,6 +6287,12 @@ public final class Settings {
Log.v(TAG, "Global.putString(name=" + name + ", value=" + value
+ " for " + userHandle);
}
+ // Global and Secure have the same access policy so we can forward writes
+ if (MOVED_TO_SECURE.contains(name)) {
+ Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global"
+ + " to android.provider.Settings.Secure, value is unchanged.");
+ return Secure.putStringForUser(resolver, name, value, userHandle);
+ }
return sNameValueCache.putStringForUser(resolver, name, value, userHandle);
}
diff --git a/core/java/android/provider/TvContract.java b/core/java/android/provider/TvContract.java
deleted file mode 100644
index 62252be..0000000
--- a/core/java/android/provider/TvContract.java
+++ /dev/null
@@ -1,596 +0,0 @@
-/*
- * 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/fingerprint/FingerprintManager.java b/core/java/android/service/fingerprint/FingerprintManager.java
index 0d14c59..2fcec52 100644
--- a/core/java/android/service/fingerprint/FingerprintManager.java
+++ b/core/java/android/service/fingerprint/FingerprintManager.java
@@ -18,6 +18,7 @@ package android.service.fingerprint;
import android.app.ActivityManagerNative;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
@@ -25,6 +26,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
/**
@@ -33,7 +35,7 @@ import android.util.Log;
public class FingerprintManager {
private static final String TAG = "FingerprintManager";
- protected static final boolean DEBUG = true;
+ private static final boolean DEBUG = true;
private static final String FINGERPRINT_SERVICE_PACKAGE = "com.android.service.fingerprint";
private static final String FINGERPRINT_SERVICE_CLASS =
"com.android.service.fingerprint.FingerprintService";
@@ -58,6 +60,7 @@ public class FingerprintManager {
private IFingerprintService mService;
private FingerprintManagerReceiver mClientReceiver;
+ private Context mContext;
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
@@ -80,6 +83,7 @@ public class FingerprintManager {
};
public FingerprintManager(Context context) {
+ mContext = context;
// Connect to service...
Intent intent = new Intent();
intent.setClassName(FINGERPRINT_SERVICE_PACKAGE, FINGERPRINT_SERVICE_CLASS);
@@ -129,6 +133,17 @@ public class FingerprintManager {
};
/**
+ * Determine whether the user has at least one fingerprint enrolled and enabled.
+ *
+ * @return true if at least one is enrolled and enabled
+ */
+ public boolean enrolledAndEnabled() {
+ ContentResolver res = mContext.getContentResolver();
+ return Settings.Secure.getInt(res, "fingerprint_enabled", 0) != 0
+ && FingerprintUtils.getFingerprintIdsForUser(res, getCurrentUserId()).length > 0;
+ }
+
+ /**
* Start the enrollment process. Timeout dictates how long to wait for the user to
* enroll a fingerprint.
*
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index d4b29d8..b3705d8 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -17,11 +17,15 @@
package android.service.notification;
import android.service.notification.StatusBarNotification;
+import android.service.notification.NotificationRankingUpdate;
/** @hide */
oneway interface INotificationListener
{
- void onListenerConnected(in String[] notificationKeys);
- void onNotificationPosted(in StatusBarNotification notification);
- void onNotificationRemoved(in StatusBarNotification notification);
+ void onListenerConnected(in NotificationRankingUpdate update);
+ void onNotificationPosted(in StatusBarNotification notification,
+ in NotificationRankingUpdate update);
+ void onNotificationRemoved(in StatusBarNotification notification,
+ in NotificationRankingUpdate update);
+ void onNotificationRankingUpdate(in NotificationRankingUpdate update);
} \ 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 3673f03..557f5a6 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -16,18 +16,26 @@
package android.service.notification;
+import android.annotation.PrivateApi;
import android.annotation.SdkConstant;
import android.app.INotificationManager;
import android.app.Service;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ParceledListSlice;
import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.UserHandle;
import android.util.Log;
+import java.util.List;
+
/**
- * A service that receives calls from the system when new notifications are posted or removed.
+ * A service that receives calls from the system when new notifications are
+ * posted or removed, or their ranking changed.
* <p>To extend this class, you must declare the service in your manifest file with
* the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission
* and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
@@ -46,9 +54,13 @@ public abstract class NotificationListenerService extends Service {
+ "[" + getClass().getSimpleName() + "]";
private INotificationListenerWrapper mWrapper = null;
+ private Ranking mRanking;
private INotificationManager mNoMan;
+ /** Only valid after a successful call to (@link registerAsService}. */
+ private int mCurrentUser;
+
/**
* The {@link Intent} that must be declared as handled by the service.
*/
@@ -86,12 +98,19 @@ public abstract class NotificationListenerService extends Service {
/**
* Implement this method to learn about when the listener is enabled and connected to
- * the notification manager. You are safe to call {@link #getActiveNotifications(String[])
+ * the notification manager. You are safe to call {@link #getActiveNotifications()}
* at this time.
- *
- * @param notificationKeys The notification keys for all currently posted notifications.
*/
- public void onListenerConnected(String[] notificationKeys) {
+ public void onListenerConnected() {
+ // optional
+ }
+
+ /**
+ * Implement this method to be notified when the notification ranking changes.
+ * <P>
+ * Call {@link #getCurrentRanking()} to retrieve the new ranking.
+ */
+ public void onNotificationRankingUpdate() {
// optional
}
@@ -202,23 +221,15 @@ public abstract class NotificationListenerService extends Service {
* Request the list of outstanding notifications (that is, those that are visible to the
* current user). Useful when you don't know what's already been posted.
*
- * @return An array of active notifications.
+ * @return An array of active notifications, sorted in natural order.
*/
public StatusBarNotification[] getActiveNotifications() {
- return getActiveNotifications(null /*all*/);
- }
-
- /**
- * Request the list of outstanding notifications (that is, those that are visible to the
- * current user). Useful when you don't know what's already been posted.
- *
- * @param keys A specific list of notification keys, or {@code null} for all.
- * @return An array of active notifications.
- */
- public StatusBarNotification[] getActiveNotifications(String[] keys) {
if (!isBound()) return null;
try {
- return getNotificationInterface().getActiveNotificationsFromListener(mWrapper, keys);
+ ParceledListSlice<StatusBarNotification> parceledList =
+ getNotificationInterface().getActiveNotificationsFromListener(mWrapper);
+ List<StatusBarNotification> list = parceledList.getList();
+ return list.toArray(new StatusBarNotification[list.size()]);
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
}
@@ -226,21 +237,20 @@ public abstract class NotificationListenerService extends Service {
}
/**
- * 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[]).
+ * Returns current ranking information.
+ *
+ * <p>
+ * The returned object represents the current ranking snapshot and only
+ * applies for currently active notifications. Hence you must retrieve a
+ * new Ranking after each notification event such as
+ * {@link #onNotificationPosted(StatusBarNotification)},
+ * {@link #onNotificationRemoved(StatusBarNotification)}, etc.
*
- * @return An array of active notification keys.
+ * @return A {@link NotificationListenerService.Ranking} object providing
+ * access to ranking information
*/
- 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;
+ public Ranking getCurrentRanking() {
+ return mRanking;
}
@Override
@@ -259,30 +269,201 @@ public abstract class NotificationListenerService extends Service {
return true;
}
+ /**
+ * Directly register this service with the Notification Manager.
+ *
+ * <p>Only system services may use this call. It will fail for non-system callers.
+ * Apps should ask the user to add their listener in Settings.
+ *
+ * @param componentName the component that will consume the notification information
+ * @param currentUser the user to use as the stream filter
+ * @hide
+ */
+ @PrivateApi
+ public void registerAsSystemService(ComponentName componentName, int currentUser)
+ throws RemoteException {
+ if (mWrapper == null) {
+ mWrapper = new INotificationListenerWrapper();
+ }
+ INotificationManager noMan = getNotificationInterface();
+ noMan.registerListener(mWrapper, componentName, currentUser);
+ mCurrentUser = currentUser;
+ }
+
+ /**
+ * Directly unregister this service from the Notification Manager.
+ *
+ * <P>This method will fail for listeners that were not registered
+ * with (@link registerAsService).
+ * @hide
+ */
+ @PrivateApi
+ public void unregisterAsSystemService() throws RemoteException {
+ if (mWrapper != null) {
+ INotificationManager noMan = getNotificationInterface();
+ noMan.unregisterListener(mWrapper, mCurrentUser);
+ }
+ }
+
private class INotificationListenerWrapper extends INotificationListener.Stub {
@Override
- public void onNotificationPosted(StatusBarNotification sbn) {
- try {
- NotificationListenerService.this.onNotificationPosted(sbn);
- } catch (Throwable t) {
- Log.w(TAG, "Error running onNotificationPosted", t);
+ public void onNotificationPosted(StatusBarNotification sbn,
+ NotificationRankingUpdate update) {
+ // protect subclass from concurrent modifications of (@link mNotificationKeys}.
+ synchronized (mWrapper) {
+ applyUpdate(update);
+ try {
+ NotificationListenerService.this.onNotificationPosted(sbn);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running onNotificationPosted", t);
+ }
}
}
@Override
- public void onNotificationRemoved(StatusBarNotification sbn) {
- try {
- NotificationListenerService.this.onNotificationRemoved(sbn);
- } catch (Throwable t) {
- Log.w(TAG, "Error running onNotificationRemoved", t);
+ public void onNotificationRemoved(StatusBarNotification sbn,
+ NotificationRankingUpdate update) {
+ // protect subclass from concurrent modifications of (@link mNotificationKeys}.
+ synchronized (mWrapper) {
+ applyUpdate(update);
+ try {
+ NotificationListenerService.this.onNotificationRemoved(sbn);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running onNotificationRemoved", t);
+ }
}
}
@Override
- public void onListenerConnected(String[] notificationKeys) {
- try {
- NotificationListenerService.this.onListenerConnected(notificationKeys);
- } catch (Throwable t) {
- Log.w(TAG, "Error running onListenerConnected", t);
+ public void onListenerConnected(NotificationRankingUpdate update) {
+ // protect subclass from concurrent modifications of (@link mNotificationKeys}.
+ synchronized (mWrapper) {
+ applyUpdate(update);
+ try {
+ NotificationListenerService.this.onListenerConnected();
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running onListenerConnected", t);
+ }
}
}
+ @Override
+ public void onNotificationRankingUpdate(NotificationRankingUpdate update)
+ throws RemoteException {
+ // protect subclass from concurrent modifications of (@link mNotificationKeys}.
+ synchronized (mWrapper) {
+ applyUpdate(update);
+ try {
+ NotificationListenerService.this.onNotificationRankingUpdate();
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running onNotificationRankingUpdate", t);
+ }
+ }
+ }
+ }
+
+ private void applyUpdate(NotificationRankingUpdate update) {
+ mRanking = new Ranking(update);
+ }
+
+ /**
+ * Provides access to ranking information on currently active
+ * notifications.
+ *
+ * <p>
+ * Note that this object represents a ranking snapshot that only applies to
+ * notifications active at the time of retrieval.
+ */
+ public static class Ranking implements Parcelable {
+ private final NotificationRankingUpdate mRankingUpdate;
+
+ private Ranking(NotificationRankingUpdate rankingUpdate) {
+ mRankingUpdate = rankingUpdate;
+ }
+
+ /**
+ * Request the list of notification keys in their current ranking
+ * order.
+ *
+ * @return An array of active notification keys, in their ranking order.
+ */
+ public String[] getOrderedKeys() {
+ return mRankingUpdate.getOrderedKeys();
+ }
+
+ /**
+ * Returns the rank of the notification with the given key, that is the
+ * index of <code>key</code> in the array of keys returned by
+ * {@link #getOrderedKeys()}.
+ *
+ * @return The rank of the notification with the given key; -1 when the
+ * given key is unknown.
+ */
+ public int getRank(String key) {
+ // TODO: Optimize.
+ String[] orderedKeys = mRankingUpdate.getOrderedKeys();
+ for (int i = 0; i < orderedKeys.length; i++) {
+ if (orderedKeys[i].equals(key)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns whether the notification with the given key was intercepted
+ * by &quot;Do not disturb&quot;.
+ */
+ public boolean isInterceptedByDoNotDisturb(String key) {
+ // TODO: Optimize.
+ for (String interceptedKey : mRankingUpdate.getDndInterceptedKeys()) {
+ if (interceptedKey.equals(key)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether the notification with the given key is an ambient
+ * notification, that is a notification that doesn't require the user's
+ * immediate attention.
+ */
+ public boolean isAmbient(String key) {
+ // TODO: Optimize.
+ int firstAmbientIndex = mRankingUpdate.getFirstAmbientIndex();
+ if (firstAmbientIndex < 0) {
+ return false;
+ }
+ String[] orderedKeys = mRankingUpdate.getOrderedKeys();
+ for (int i = firstAmbientIndex; i < orderedKeys.length; i++) {
+ if (orderedKeys[i].equals(key)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // ----------- Parcelable
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mRankingUpdate, flags);
+ }
+
+ public static final Creator<Ranking> CREATOR = new Creator<Ranking>() {
+ @Override
+ public Ranking createFromParcel(Parcel source) {
+ NotificationRankingUpdate rankingUpdate = source.readParcelable(null);
+ return new Ranking(rankingUpdate);
+ }
+
+ @Override
+ public Ranking[] newArray(int size) {
+ return new Ranking[size];
+ }
+ };
}
}
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.aidl b/core/java/android/service/notification/NotificationRankingUpdate.aidl
new file mode 100644
index 0000000..1393cb9
--- /dev/null
+++ b/core/java/android/service/notification/NotificationRankingUpdate.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.service.notification;
+
+parcelable NotificationRankingUpdate;
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
new file mode 100644
index 0000000..4b13d95
--- /dev/null
+++ b/core/java/android/service/notification/NotificationRankingUpdate.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.notification;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public class NotificationRankingUpdate implements Parcelable {
+ // TODO: Support incremental updates.
+ private final String[] mKeys;
+ private final String[] mDndInterceptedKeys;
+ private final int mFirstAmbientIndex;
+
+ public NotificationRankingUpdate(String[] keys, String[] dndInterceptedKeys,
+ int firstAmbientIndex) {
+ mKeys = keys;
+ mFirstAmbientIndex = firstAmbientIndex;
+ mDndInterceptedKeys = dndInterceptedKeys;
+ }
+
+ public NotificationRankingUpdate(Parcel in) {
+ mKeys = in.readStringArray();
+ mFirstAmbientIndex = in.readInt();
+ mDndInterceptedKeys = in.readStringArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeStringArray(mKeys);
+ out.writeInt(mFirstAmbientIndex);
+ out.writeStringArray(mDndInterceptedKeys);
+ }
+
+ public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
+ = new Parcelable.Creator<NotificationRankingUpdate>() {
+ public NotificationRankingUpdate createFromParcel(Parcel parcel) {
+ return new NotificationRankingUpdate(parcel);
+ }
+
+ public NotificationRankingUpdate[] newArray(int size) {
+ return new NotificationRankingUpdate[size];
+ }
+ };
+
+ public String[] getOrderedKeys() {
+ return mKeys;
+ }
+
+ public int getFirstAmbientIndex() {
+ return mFirstAmbientIndex;
+ }
+
+ public String[] getDndInterceptedKeys() {
+ return mDndInterceptedKeys;
+ }
+}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 846e292..d02fc7b 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -41,12 +41,18 @@ public class ZenModeConfig implements Parcelable {
public static final String SLEEP_MODE_NIGHTS = "nights";
public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights";
+ public static final int SOURCE_ANYONE = 0;
+ public static final int SOURCE_CONTACT = 1;
+ public static final int SOURCE_STAR = 2;
+ public static final int MAX_SOURCE = SOURCE_STAR;
+
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 ALLOW_ATT_FROM = "from";
private static final String SLEEP_TAG = "sleep";
private static final String SLEEP_ATT_MODE = "mode";
@@ -61,6 +67,7 @@ public class ZenModeConfig implements Parcelable {
public boolean allowCalls;
public boolean allowMessages;
+ public int allowFrom = SOURCE_ANYONE;
public String sleepMode;
public int sleepStartHour;
@@ -92,6 +99,7 @@ public class ZenModeConfig implements Parcelable {
conditionIds = new Uri[len];
source.readTypedArray(conditionIds, Uri.CREATOR);
}
+ allowFrom = source.readInt();
}
@Override
@@ -120,6 +128,7 @@ public class ZenModeConfig implements Parcelable {
} else {
dest.writeInt(0);
}
+ dest.writeInt(allowFrom);
}
@Override
@@ -127,6 +136,7 @@ public class ZenModeConfig implements Parcelable {
return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
.append("allowCalls=").append(allowCalls)
.append(",allowMessages=").append(allowMessages)
+ .append(",allowFrom=").append(sourceToString(allowFrom))
.append(",sleepMode=").append(sleepMode)
.append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute)
.append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute)
@@ -137,6 +147,19 @@ public class ZenModeConfig implements Parcelable {
.append(']').toString();
}
+ public static String sourceToString(int source) {
+ switch (source) {
+ case SOURCE_ANYONE:
+ return "anyone";
+ case SOURCE_CONTACT:
+ return "contacts";
+ case SOURCE_STAR:
+ return "stars";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
@Override
public boolean equals(Object o) {
if (!(o instanceof ZenModeConfig)) return false;
@@ -144,6 +167,7 @@ public class ZenModeConfig implements Parcelable {
final ZenModeConfig other = (ZenModeConfig) o;
return other.allowCalls == allowCalls
&& other.allowMessages == allowMessages
+ && other.allowFrom == allowFrom
&& Objects.equals(other.sleepMode, sleepMode)
&& other.sleepStartHour == sleepStartHour
&& other.sleepStartMinute == sleepStartMinute
@@ -155,8 +179,8 @@ public class ZenModeConfig implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(allowCalls, allowMessages, sleepMode, sleepStartHour,
- sleepStartMinute, sleepEndHour, sleepEndMinute,
+ return Objects.hash(allowCalls, allowMessages, allowFrom, sleepMode,
+ sleepStartHour, sleepStartMinute, sleepEndHour, sleepEndMinute,
Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds));
}
@@ -191,6 +215,10 @@ public class ZenModeConfig implements Parcelable {
if (ALLOW_TAG.equals(tag)) {
rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
+ rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE);
+ if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) {
+ throw new IndexOutOfBoundsException("bad source in config:" + rt.allowFrom);
+ }
} else if (SLEEP_TAG.equals(tag)) {
final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE);
rt.sleepMode = (SLEEP_MODE_NIGHTS.equals(mode)
@@ -224,6 +252,7 @@ public class ZenModeConfig implements Parcelable {
out.startTag(null, ALLOW_TAG);
out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls));
out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages));
+ out.attribute(null, ALLOW_ATT_FROM, Integer.toString(allowFrom));
out.endTag(null, ALLOW_TAG);
out.startTag(null, SLEEP_TAG);
diff --git a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
index c346771..9e4c2bf 100644
--- a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
+++ b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
@@ -23,6 +23,6 @@ import android.os.UserHandle;
* @hide
*/
oneway interface ITrustAgentServiceCallback {
- void enableTrust(String message, long durationMs, boolean initiatedByUser);
+ void grantTrust(CharSequence 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
index d5ce429..98f70f4 100644
--- a/core/java/android/service/trust/TrustAgentService.java
+++ b/core/java/android/service/trust/TrustAgentService.java
@@ -16,12 +16,17 @@
package android.service.trust;
+import android.Manifest;
import android.annotation.SdkConstant;
import android.app.Service;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.Log;
import android.util.Slog;
/**
@@ -29,12 +34,12 @@ import android.util.Slog;
* 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
+ * the {@link android.Manifest.permission#BIND_TRUST_AGENT} 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">
+ * android:permission="android.permission.BIND_TRUST_AGENT">
* &lt;intent-filter>
* &lt;action android:name="android.service.trust.TrustAgentService" />
* &lt;/intent-filter>
@@ -47,7 +52,7 @@ import android.util.Slog;
* {@link android.R.styleable#TrustAgent}. For example:</p>
*
* <pre>
- * &lt;trust_agent xmlns:android="http://schemas.android.com/apk/res/android"
+ * &lt;trust-agent xmlns:android="http://schemas.android.com/apk/res/android"
* android:settingsActivity=".TrustAgentSettings" /></pre>
*/
public class TrustAgentService extends Service {
@@ -83,12 +88,28 @@ public class TrustAgentService extends Service {
};
};
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ ComponentName component = new ComponentName(this, getClass());
+ try {
+ ServiceInfo serviceInfo = getPackageManager().getServiceInfo(component, 0 /* flags */);
+ if (!Manifest.permission.BIND_TRUST_AGENT.equals(serviceInfo.permission)) {
+ throw new IllegalStateException(component.flattenToShortString()
+ + " is not declared with the permission "
+ + "\"" + Manifest.permission.BIND_TRUST_AGENT + "\"");
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Can't get ServiceInfo for " + component.toShortString());
+ }
+ }
+
/**
* Called when the user attempted to authenticate on the device.
*
* @param successful true if the attempt succeeded
*/
- protected void onUnlockAttempt(boolean successful) {
+ public void onUnlockAttempt(boolean successful) {
}
private void onError(String msg) {
@@ -96,7 +117,7 @@ public class TrustAgentService extends Service {
}
/**
- * Call to enable trust on the device.
+ * Call to grant 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
@@ -104,10 +125,10 @@ public class TrustAgentService extends Service {
* @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) {
+ public final void grantTrust(CharSequence message, long durationMs, boolean initiatedByUser) {
if (mCallback != null) {
try {
- mCallback.enableTrust(message, durationMs, initiatedByUser);
+ mCallback.grantTrust(message.toString(), durationMs, initiatedByUser);
} catch (RemoteException e) {
onError("calling enableTrust()");
}
@@ -117,7 +138,7 @@ public class TrustAgentService extends Service {
/**
* Call to revoke trust on the device.
*/
- protected final void revokeTrust() {
+ public final void revokeTrust() {
if (mCallback != null) {
try {
mCallback.revokeTrust();
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index a83544d..2e9077a 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -20,8 +20,8 @@ import android.app.Dialog;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Rect;
import android.graphics.Region;
import android.inputmethodservice.SoftInputWindow;
import android.os.Binder;
@@ -32,6 +32,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -46,9 +47,22 @@ import com.android.internal.app.IVoiceInteractorRequest;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import java.lang.ref.WeakReference;
+
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+/**
+ * An active voice interaction session, providing a facility for the implementation
+ * to interact with the user in the voice interaction layer. This interface is no shown
+ * by default, but you can request that it be shown with {@link #showWindow()}, which
+ * will result in a later call to {@link #onCreateContentView()} in which the UI can be
+ * built
+ *
+ * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish}
+ * when done. It can also initiate voice interactions with applications by calling
+ * {@link #startVoiceActivity}</p>.
+ */
public abstract class VoiceInteractionSession implements KeyEvent.Callback {
static final String TAG = "VoiceInteractionSession";
static final boolean DEBUG = true;
@@ -79,11 +93,14 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
final Insets mTmpInsets = new Insets();
final int[] mTmpLocation = new int[2];
+ final WeakReference<VoiceInteractionSession> mWeakRef
+ = new WeakReference<VoiceInteractionSession>(this);
+
final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() {
@Override
public IVoiceInteractorRequest startConfirmation(String callingPackage,
- IVoiceInteractorCallback callback, String prompt, Bundle extras) {
- Request request = findRequest(callback, true);
+ IVoiceInteractorCallback callback, CharSequence prompt, Bundle extras) {
+ Request request = newRequest(callback);
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_CONFIRMATION,
new Caller(callingPackage, Binder.getCallingUid()), request,
prompt, extras));
@@ -91,9 +108,19 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
@Override
+ public IVoiceInteractorRequest startAbortVoice(String callingPackage,
+ IVoiceInteractorCallback callback, CharSequence message, Bundle extras) {
+ Request request = newRequest(callback);
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_ABORT_VOICE,
+ new Caller(callingPackage, Binder.getCallingUid()), request,
+ message, extras));
+ return request.mInterface;
+ }
+
+ @Override
public IVoiceInteractorRequest startCommand(String callingPackage,
IVoiceInteractorCallback callback, String command, Bundle extras) {
- Request request = findRequest(callback, true);
+ Request request = newRequest(callback);
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMMAND,
new Caller(callingPackage, Binder.getCallingUid()), request,
command, extras));
@@ -142,29 +169,60 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() {
@Override
public void cancel() throws RemoteException {
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this));
+ VoiceInteractionSession session = mSession.get();
+ if (session != null) {
+ session.mHandlerCaller.sendMessage(
+ session.mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this));
+ }
}
};
final IVoiceInteractorCallback mCallback;
- final HandlerCaller mHandlerCaller;
- Request(IVoiceInteractorCallback callback, HandlerCaller handlerCaller) {
+ final WeakReference<VoiceInteractionSession> mSession;
+
+ Request(IVoiceInteractorCallback callback, VoiceInteractionSession session) {
mCallback = callback;
- mHandlerCaller = handlerCaller;
+ mSession = session.mWeakRef;
+ }
+
+ void finishRequest() {
+ VoiceInteractionSession session = mSession.get();
+ if (session == null) {
+ throw new IllegalStateException("VoiceInteractionSession has been destroyed");
+ }
+ Request req = session.removeRequest(mInterface.asBinder());
+ if (req == null) {
+ throw new IllegalStateException("Request not active: " + this);
+ } else if (req != this) {
+ throw new IllegalStateException("Current active request " + req
+ + " not same as calling request " + this);
+ }
}
public void sendConfirmResult(boolean confirmed, Bundle result) {
try {
if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
+ " confirmed=" + confirmed + " result=" + result);
+ finishRequest();
mCallback.deliverConfirmationResult(mInterface, confirmed, result);
} catch (RemoteException e) {
}
}
+ public void sendAbortVoiceResult(Bundle result) {
+ try {
+ if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
+ + " result=" + result);
+ finishRequest();
+ mCallback.deliverAbortVoiceResult(mInterface, result);
+ } catch (RemoteException e) {
+ }
+ }
+
public void sendCommandResult(boolean complete, Bundle result) {
try {
if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface
+ " result=" + result);
+ finishRequest();
mCallback.deliverCommandResult(mInterface, complete, result);
} catch (RemoteException e) {
}
@@ -173,6 +231,7 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
public void sendCancelResult() {
try {
if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface);
+ finishRequest();
mCallback.deliverCancel(mInterface);
} catch (RemoteException e) {
}
@@ -190,9 +249,10 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
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;
+ static final int MSG_START_ABORT_VOICE = 2;
+ static final int MSG_START_COMMAND = 3;
+ static final int MSG_SUPPORTS_COMMANDS = 4;
+ static final int MSG_CANCEL = 5;
static final int MSG_TASK_STARTED = 100;
static final int MSG_TASK_FINISHED = 101;
@@ -208,9 +268,16 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
args = (SomeArgs)msg.obj;
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,
+ onConfirm((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3,
(Bundle)args.arg4);
break;
+ case MSG_START_ABORT_VOICE:
+ args = (SomeArgs)msg.obj;
+ if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + ((Request) args.arg2).mInterface
+ + " message=" + args.arg3 + " extras=" + args.arg4);
+ onAbortVoice((Caller) args.arg1, (Request) args.arg2, (CharSequence) args.arg3,
+ (Bundle) args.arg4);
+ break;
case MSG_START_COMMAND:
args = (SomeArgs)msg.obj;
if (DEBUG) Log.d(TAG, "onCommand: req=" + ((Request) args.arg2).mInterface
@@ -262,14 +329,14 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
*/
public static final class Insets {
/**
- * This is the top part of the UI that is the main content. It is
+ * This is the part of the UI that is the main content. It is
* used to determine the basic space needed, to resize/pan the
* application behind. It is assumed that this inset does not
* change very much, since any change will cause a full resize/pan
* of the application behind. This value is relative to the top edge
* of the input method window.
*/
- public int contentTopInsets;
+ public final Rect contentInsets = new Rect();
/**
* This is the region of the UI that is touchable. It is used when
@@ -311,7 +378,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
new ViewTreeObserver.OnComputeInternalInsetsListener() {
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
onComputeInsets(mTmpInsets);
- info.contentInsets.top = info.visibleInsets.top = mTmpInsets.contentTopInsets;
+ info.contentInsets.set(mTmpInsets.contentInsets);
+ info.visibleInsets.set(mTmpInsets.contentInsets);
info.touchableRegion.set(mTmpInsets.touchableRegion);
info.setTouchableInsets(mTmpInsets.touchableInsets);
}
@@ -327,18 +395,20 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
mCallbacks, true);
}
- Request findRequest(IVoiceInteractorCallback callback, boolean newRequest) {
+ Request newRequest(IVoiceInteractorCallback callback) {
+ synchronized (this) {
+ Request req = new Request(callback, this);
+ mActiveRequests.put(req.mInterface.asBinder(), req);
+ return req;
+ }
+ }
+
+ Request removeRequest(IBinder reqInterface) {
synchronized (this) {
- Request req = mActiveRequests.get(callback.asBinder());
+ Request req = mActiveRequests.get(reqInterface);
if (req != null) {
- if (newRequest) {
- throw new IllegalArgumentException("Given request callback " + callback
- + " is already active");
- }
- return req;
+ mActiveRequests.remove(req);
}
- req = new Request(callback, mHandlerCaller);
- mActiveRequests.put(callback.asBinder(), req);
return req;
}
}
@@ -423,11 +493,34 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
mTheme = theme;
}
+ /**
+ * Ask that a new activity be started for voice interaction. This will create a
+ * new dedicated task in the activity manager for this voice interaction session;
+ * this means that {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK}
+ * will be set for you to make it a new task.
+ *
+ * <p>The newly started activity will be displayed to the user in a special way, as
+ * a layer under the voice interaction UI.</p>
+ *
+ * <p>As the voice activity runs, it can retrieve a {@link android.app.VoiceInteractor}
+ * through which it can perform voice interactions through your session. These requests
+ * for voice interactions will appear as callbacks on {@link #onGetSupportedCommands},
+ * {@link #onConfirm}, {@link #onCommand}, and {@link #onCancel}.
+ *
+ * <p>You will receive a call to {@link #onTaskStarted} when the task starts up
+ * and {@link #onTaskFinished} when the last activity has finished.
+ *
+ * @param intent The Intent to start this voice interaction. The given Intent will
+ * always have {@link Intent#CATEGORY_VOICE Intent.CATEGORY_VOICE} added to it, since
+ * this is part of a voice interaction.
+ */
public void startVoiceActivity(Intent intent) {
if (mToken == null) {
throw new IllegalStateException("Can't call before onCreate()");
}
try {
+ intent.migrateExtraStreamToClipData();
+ intent.prepareToLeaveProcess();
int res = mSystemService.startVoiceActivity(mToken, intent,
intent.resolveType(mContext.getContentResolver()));
Instrumentation.checkStartActivityResult(res, intent);
@@ -435,14 +528,23 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
}
+ /**
+ * Convenience for inflating views.
+ */
public LayoutInflater getLayoutInflater() {
return mInflater;
}
+ /**
+ * Retrieve the window being used to show the session's UI.
+ */
public Dialog getWindow() {
return mWindow;
}
+ /**
+ * Finish the session.
+ */
public void finish() {
if (mToken == null) {
throw new IllegalStateException("Can't call before onCreate()");
@@ -454,22 +556,35 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
}
+ /**
+ * Initiatize a new session.
+ *
+ * @param args The arguments that were supplied to
+ * {@link VoiceInteractionService#startSession VoiceInteractionService.startSession}.
+ */
public void onCreate(Bundle args) {
mTheme = mTheme != 0 ? mTheme
: com.android.internal.R.style.Theme_DeviceDefault_VoiceInteractionSession;
mInflater = (LayoutInflater)mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme,
- mCallbacks, this, mDispatcherState, true);
+ mCallbacks, this, mDispatcherState,
+ WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.TOP, true);
mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
initViews();
mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
mWindow.setToken(mToken);
}
+ /**
+ * Last callback to the session as it is being finished.
+ */
public void onDestroy() {
}
+ /**
+ * Hook in which to create the session's UI.
+ */
public View onCreateContentView() {
return null;
}
@@ -502,6 +617,11 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
finish();
}
+ /**
+ * Sessions automatically watch for requests that all system UI be closed (such as when
+ * the user presses HOME), which will appear here. The default implementation always
+ * calls {@link #finish}.
+ */
public void onCloseSystemDialogs() {
finish();
}
@@ -517,20 +637,106 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
int[] loc = mTmpLocation;
View decor = getWindow().getWindow().getDecorView();
decor.getLocationInWindow(loc);
- outInsets.contentTopInsets = loc[1];
+ outInsets.contentInsets.top = 0;
+ outInsets.contentInsets.left = 0;
+ outInsets.contentInsets.right = 0;
+ outInsets.contentInsets.bottom = 0;
outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_FRAME;
outInsets.touchableRegion.setEmpty();
}
+ /**
+ * Called when a task initiated by {@link #startVoiceActivity(android.content.Intent)}
+ * has actually started.
+ *
+ * @param intent The original {@link Intent} supplied to
+ * {@link #startVoiceActivity(android.content.Intent)}.
+ * @param taskId Unique ID of the now running task.
+ */
public void onTaskStarted(Intent intent, int taskId) {
}
+ /**
+ * Called when the last activity of a task initiated by
+ * {@link #startVoiceActivity(android.content.Intent)} has finished. The default
+ * implementation calls {@link #finish()} on the assumption that this represents
+ * the completion of a voice action. You can override the implementation if you would
+ * like a different behavior.
+ *
+ * @param intent The original {@link Intent} supplied to
+ * {@link #startVoiceActivity(android.content.Intent)}.
+ * @param taskId Unique ID of the finished task.
+ */
public void onTaskFinished(Intent intent, int taskId) {
finish();
}
- public abstract boolean[] onGetSupportedCommands(Caller caller, String[] commands);
- public abstract void onConfirm(Caller caller, Request request, String prompt, Bundle extras);
+ /**
+ * Request to query for what extended commands the session supports.
+ *
+ * @param caller Who is making the request.
+ * @param commands An array of commands that are being queried.
+ * @return Return an array of booleans indicating which of each entry in the
+ * command array is supported. A true entry in the array indicates the command
+ * is supported; false indicates it is not. The default implementation returns
+ * an array of all false entries.
+ */
+ public boolean[] onGetSupportedCommands(Caller caller, String[] commands) {
+ return new boolean[commands.length];
+ }
+
+ /**
+ * Request to confirm with the user before proceeding with an unrecoverable operation,
+ * corresponding to a {@link android.app.VoiceInteractor.ConfirmationRequest
+ * VoiceInteractor.ConfirmationRequest}.
+ *
+ * @param caller Who is making the request.
+ * @param request The active request.
+ * @param prompt The prompt informing the user of what will happen, as per
+ * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}.
+ * @param extras Any additional information, as per
+ * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}.
+ */
+ public abstract void onConfirm(Caller caller, Request request, CharSequence prompt,
+ Bundle extras);
+
+ /**
+ * Request to abort the voice interaction session because the voice activity can not
+ * complete its interaction using voice. Corresponds to
+ * {@link android.app.VoiceInteractor.AbortVoiceRequest
+ * VoiceInteractor.AbortVoiceRequest}. The default implementation just sends an empty
+ * confirmation back to allow the activity to exit.
+ *
+ * @param caller Who is making the request.
+ * @param request The active request.
+ * @param message The message informing the user of the problem, as per
+ * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
+ * @param extras Any additional information, as per
+ * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
+ */
+ public void onAbortVoice(Caller caller, Request request, CharSequence message, Bundle extras) {
+ request.sendAbortVoiceResult(null);
+ }
+
+ /**
+ * Process an arbitrary extended command from the caller,
+ * corresponding to a {@link android.app.VoiceInteractor.CommandRequest
+ * VoiceInteractor.CommandRequest}.
+ *
+ * @param caller Who is making the request.
+ * @param request The active request.
+ * @param command The command that is being executed, as per
+ * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}.
+ * @param extras Any additional information, as per
+ * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}.
+ */
public abstract void onCommand(Caller caller, Request request, String command, Bundle extras);
+
+ /**
+ * Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request}
+ * that was previously delivered to {@link #onConfirm} or {@link #onCommand}.
+ *
+ * @param request The request that is being canceled.
+ */
public abstract void onCancel(Request request);
}
diff --git a/core/java/android/speech/tts/Markup.java b/core/java/android/speech/tts/Markup.java
new file mode 100644
index 0000000..c886e5d
--- /dev/null
+++ b/core/java/android/speech/tts/Markup.java
@@ -0,0 +1,537 @@
+package android.speech.tts;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that provides markup to a synthesis request to control aspects of speech.
+ * <p>
+ * Markup itself is a feature agnostic data format; the {@link Utterance} class defines the currently
+ * available set of features and should be used to construct instances of the Markup class.
+ * </p>
+ * <p>
+ * A marked up sentence is a tree. Each node has a type, an optional plain text, a set of
+ * parameters, and a list of children.
+ * The <b>type</b> defines what it contains, e.g. "text", "date", "measure", etc. A Markup node
+ * can be either a part of sentence (often a leaf node), or node altering some property of its
+ * children (node with children). The top level node has to be of type "utterance" and its children
+ * are synthesized in order.
+ * The <b>plain text</b> is optional except for the top level node. If the synthesis engine does not
+ * support Markup at all, it should use the plain text of the top level node. If an engine does not
+ * recognize or support a node type, it will try to use the plain text of that node if provided. If
+ * the plain text is null, it will synthesize its children in order.
+ * <b>Parameters</b> are key-value pairs specific to each node type. In case of a date node the
+ * parameters may be for example "month: 7" and "day: 10".
+ * The <b>nested markups</b> are children and can for example be used to nest semiotic classes (a
+ * measure may have a node of type "decimal" as its child) or to modify some property of its
+ * children. See "plain text" on how they are processed if the parent of the children is unknown to
+ * the engine.
+ * <p>
+ */
+public final class Markup implements Parcelable {
+
+ private String mType;
+ private String mPlainText;
+
+ private Bundle mParameters = new Bundle();
+ private List<Markup> mNestedMarkups = new ArrayList<Markup>();
+
+ private static final String TYPE = "type";
+ private static final String PLAIN_TEXT = "plain_text";
+ private static final String MARKUP = "markup";
+
+ private static final String IDENTIFIER_REGEX = "([0-9a-z_]+)";
+ private static final Pattern legalIdentifierPattern = Pattern.compile(IDENTIFIER_REGEX);
+
+ /**
+ * Constructs an empty markup.
+ */
+ public Markup() {}
+
+ /**
+ * Constructs a markup of the given type.
+ */
+ public Markup(String type) {
+ setType(type);
+ }
+
+ /**
+ * Returns the type of this node; can be null.
+ */
+ public String getType() {
+ return mType;
+ }
+
+ /**
+ * Sets the type of this node. can be null. May only contain [0-9a-z_].
+ */
+ public void setType(String type) {
+ if (type != null) {
+ Matcher matcher = legalIdentifierPattern.matcher(type);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Type cannot be empty and may only contain " +
+ "0-9, a-z and underscores.");
+ }
+ }
+ mType = type;
+ }
+
+ /**
+ * Returns this node's plain text; can be null.
+ */
+ public String getPlainText() {
+ return mPlainText;
+ }
+
+ /**
+ * Sets this nodes's plain text; can be null.
+ */
+ public void setPlainText(String plainText) {
+ mPlainText = plainText;
+ }
+
+ /**
+ * Adds or modifies a parameter.
+ * @param key The key; may only contain [0-9a-z_] and cannot be "type" or "plain_text".
+ * @param value The value.
+ * @throws An {@link IllegalArgumentException} if the key is null or empty.
+ * @return this
+ */
+ public Markup setParameter(String key, String value) {
+ if (key == null || key.isEmpty()) {
+ throw new IllegalArgumentException("Key cannot be null or empty.");
+ }
+ if (key.equals("type")) {
+ throw new IllegalArgumentException("Key cannot be \"type\".");
+ }
+ if (key.equals("plain_text")) {
+ throw new IllegalArgumentException("Key cannot be \"plain_text\".");
+ }
+ Matcher matcher = legalIdentifierPattern.matcher(key);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Key may only contain 0-9, a-z and underscores.");
+ }
+
+ if (value != null) {
+ mParameters.putString(key, value);
+ } else {
+ removeParameter(key);
+ }
+ return this;
+ }
+
+ /**
+ * Removes the parameter with the given key
+ */
+ public void removeParameter(String key) {
+ mParameters.remove(key);
+ }
+
+ /**
+ * Returns the value of the parameter.
+ * @param key The parameter key.
+ * @return The value of the parameter or null if the parameter is not set.
+ */
+ public String getParameter(String key) {
+ return mParameters.getString(key);
+ }
+
+ /**
+ * Returns the number of parameters that have been set.
+ */
+ public int parametersSize() {
+ return mParameters.size();
+ }
+
+ /**
+ * Appends a child to the list of children
+ * @param markup The child.
+ * @return This instance.
+ * @throws {@link IllegalArgumentException} if markup is null.
+ */
+ public Markup addNestedMarkup(Markup markup) {
+ if (markup == null) {
+ throw new IllegalArgumentException("Nested markup cannot be null");
+ }
+ mNestedMarkups.add(markup);
+ return this;
+ }
+
+ /**
+ * Removes the given node from its children.
+ * @param markup The child to remove.
+ * @return True if this instance was modified by this operation, false otherwise.
+ */
+ public boolean removeNestedMarkup(Markup markup) {
+ return mNestedMarkups.remove(markup);
+ }
+
+ /**
+ * Returns the index'th child.
+ * @param i The index of the child.
+ * @return The child.
+ * @throws {@link IndexOutOfBoundsException} if i < 0 or i >= nestedMarkupSize()
+ */
+ public Markup getNestedMarkup(int i) {
+ return mNestedMarkups.get(i);
+ }
+
+
+ /**
+ * Returns the number of children.
+ */
+ public int nestedMarkupSize() {
+ return mNestedMarkups.size();
+ }
+
+ /**
+ * Returns a string representation of this Markup instance. Can be deserialized back to a Markup
+ * instance with markupFromString().
+ */
+ public String toString() {
+ StringBuilder out = new StringBuilder();
+ if (mType != null) {
+ out.append(TYPE + ": \"" + mType + "\"");
+ }
+ if (mPlainText != null) {
+ out.append(out.length() > 0 ? " " : "");
+ out.append(PLAIN_TEXT + ": \"" + escapeQuotedString(mPlainText) + "\"");
+ }
+ // Sort the parameters alphabetically by key so we have a stable output.
+ SortedMap<String, String> sortedMap = new TreeMap<String, String>();
+ for (String key : mParameters.keySet()) {
+ sortedMap.put(key, mParameters.getString(key));
+ }
+ for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
+ out.append(out.length() > 0 ? " " : "");
+ out.append(entry.getKey() + ": \"" + escapeQuotedString(entry.getValue()) + "\"");
+ }
+ for (Markup m : mNestedMarkups) {
+ out.append(out.length() > 0 ? " " : "");
+ String nestedStr = m.toString();
+ if (nestedStr.isEmpty()) {
+ out.append(MARKUP + " {}");
+ } else {
+ out.append(MARKUP + " { " + m.toString() + " }");
+ }
+ }
+ return out.toString();
+ }
+
+ /**
+ * Escapes backslashes and double quotes in the plain text and parameter values before this
+ * instance is written to a string.
+ * @param str The string to escape.
+ * @return The escaped string.
+ */
+ private static String escapeQuotedString(String str) {
+ StringBuilder out = new StringBuilder();
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if (c == '"') {
+ out.append("\\\"");
+ } else if (str.charAt(i) == '\\') {
+ out.append("\\\\");
+ } else {
+ out.append(c);
+ }
+ }
+ return out.toString();
+ }
+
+ /**
+ * The reverse of the escape method, returning plain text and parameter values to their original
+ * form.
+ * @param str An escaped string.
+ * @return The unescaped string.
+ */
+ private static String unescapeQuotedString(String str) {
+ StringBuilder out = new StringBuilder();
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if (c == '\\') {
+ i++;
+ if (i >= str.length()) {
+ throw new IllegalArgumentException("Unterminated escape sequence in string: " +
+ str);
+ }
+ c = str.charAt(i);
+ if (c == '\\') {
+ out.append("\\");
+ } else if (c == '"') {
+ out.append("\"");
+ } else {
+ throw new IllegalArgumentException("Unsupported escape sequence: \\" + c +
+ " in string " + str);
+ }
+ } else {
+ out.append(c);
+ }
+ }
+ return out.toString();
+ }
+
+ /**
+ * Returns true if the given string consists only of whitespace.
+ * @param str The string to check.
+ * @return True if the given string consists only of whitespace.
+ */
+ private static boolean isWhitespace(String str) {
+ return Pattern.matches("\\s*", str);
+ }
+
+ /**
+ * Parses the given string, and overrides the values of this instance with those contained
+ * in the given string.
+ * @param str The string to parse; can have superfluous whitespace.
+ * @return An empty string on success, else the remainder of the string that could not be
+ * parsed.
+ */
+ private String fromReadableString(String str) {
+ while (!isWhitespace(str)) {
+ String newStr = matchValue(str);
+ if (newStr == null) {
+ newStr = matchMarkup(str);
+
+ if (newStr == null) {
+ return str;
+ }
+ }
+ str = newStr;
+ }
+ return "";
+ }
+
+ // Matches: key : "value"
+ // where key is an identifier and value can contain escaped quotes
+ // there may be superflouous whitespace
+ // The value string may contain quotes and backslashes.
+ private static final String OPTIONAL_WHITESPACE = "\\s*";
+ private static final String VALUE_REGEX = "((\\\\.|[^\\\"])*)";
+ private static final String KEY_VALUE_REGEX =
+ "\\A" + OPTIONAL_WHITESPACE + // start of string
+ IDENTIFIER_REGEX + OPTIONAL_WHITESPACE + ":" + OPTIONAL_WHITESPACE + // key:
+ "\"" + VALUE_REGEX + "\""; // "value"
+ private static final Pattern KEY_VALUE_PATTERN = Pattern.compile(KEY_VALUE_REGEX);
+
+ /**
+ * Tries to match a key-value pair at the start of the string. If found, add that as a parameter
+ * of this instance.
+ * @param str The string to parse.
+ * @return The remainder of the string without the parsed key-value pair on success, else null.
+ */
+ private String matchValue(String str) {
+ // Matches: key: "value"
+ Matcher matcher = KEY_VALUE_PATTERN.matcher(str);
+ if (!matcher.find()) {
+ return null;
+ }
+ String key = matcher.group(1);
+ String value = matcher.group(2);
+
+ if (key == null || value == null) {
+ return null;
+ }
+ String unescapedValue = unescapeQuotedString(value);
+ if (key.equals(TYPE)) {
+ this.mType = unescapedValue;
+ } else if (key.equals(PLAIN_TEXT)) {
+ this.mPlainText = unescapedValue;
+ } else {
+ setParameter(key, unescapedValue);
+ }
+
+ return str.substring(matcher.group(0).length());
+ }
+
+ // matches 'markup {'
+ private static final Pattern OPEN_MARKUP_PATTERN =
+ Pattern.compile("\\A" + OPTIONAL_WHITESPACE + MARKUP + OPTIONAL_WHITESPACE + "\\{");
+ // matches '}'
+ private static final Pattern CLOSE_MARKUP_PATTERN =
+ Pattern.compile("\\A" + OPTIONAL_WHITESPACE + "\\}");
+
+ /**
+ * Tries to parse a Markup specification from the start of the string. If so, add that markup to
+ * the list of nested Markup's of this instance.
+ * @param str The string to parse.
+ * @return The remainder of the string without the parsed Markup on success, else null.
+ */
+ private String matchMarkup(String str) {
+ // find and strip "markup {"
+ Matcher matcher = OPEN_MARKUP_PATTERN.matcher(str);
+
+ if (!matcher.find()) {
+ return null;
+ }
+ String strRemainder = str.substring(matcher.group(0).length());
+ // parse and strip markup contents
+ Markup nestedMarkup = new Markup();
+ strRemainder = nestedMarkup.fromReadableString(strRemainder);
+
+ // find and strip "}"
+ Matcher matcherClose = CLOSE_MARKUP_PATTERN.matcher(strRemainder);
+ if (!matcherClose.find()) {
+ return null;
+ }
+ strRemainder = strRemainder.substring(matcherClose.group(0).length());
+
+ // Everything parsed, add markup
+ this.addNestedMarkup(nestedMarkup);
+
+ // Return remainder
+ return strRemainder;
+ }
+
+ /**
+ * Returns a Markup instance from the string representation generated by toString().
+ * @param string The string representation generated by toString().
+ * @return The new Markup instance.
+ * @throws {@link IllegalArgumentException} if the input cannot be correctly parsed.
+ */
+ public static Markup markupFromString(String string) throws IllegalArgumentException {
+ Markup m = new Markup();
+ if (m.fromReadableString(string).isEmpty()) {
+ return m;
+ } else {
+ throw new IllegalArgumentException("Cannot parse input to Markup");
+ }
+ }
+
+ /**
+ * Compares the specified object with this Markup for equality.
+ * @return True if the given object is a Markup instance with the same type, plain text,
+ * parameters and the nested markups are also equal to each other and in the same order.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if ( this == o ) return true;
+ if ( !(o instanceof Markup) ) return false;
+ Markup m = (Markup) o;
+
+ if (nestedMarkupSize() != this.nestedMarkupSize()) {
+ return false;
+ }
+
+ if (!(mType == null ? m.mType == null : mType.equals(m.mType))) {
+ return false;
+ }
+ if (!(mPlainText == null ? m.mPlainText == null : mPlainText.equals(m.mPlainText))) {
+ return false;
+ }
+ if (!equalBundles(mParameters, m.mParameters)) {
+ return false;
+ }
+
+ for (int i = 0; i < this.nestedMarkupSize(); i++) {
+ if (!mNestedMarkups.get(i).equals(m.mNestedMarkups.get(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if two bundles are equal to each other. Used by equals(o).
+ */
+ private boolean equalBundles(Bundle one, Bundle two) {
+ if (one == null || two == null) {
+ return false;
+ }
+
+ if(one.size() != two.size()) {
+ return false;
+ }
+
+ Set<String> valuesOne = one.keySet();
+ for(String key : valuesOne) {
+ Object valueOne = one.get(key);
+ Object valueTwo = two.get(key);
+ if (valueOne instanceof Bundle && valueTwo instanceof Bundle &&
+ !equalBundles((Bundle) valueOne, (Bundle) valueTwo)) {
+ return false;
+ } else if (valueOne == null) {
+ if (valueTwo != null || !two.containsKey(key)) {
+ return false;
+ }
+ } else if(!valueOne.equals(valueTwo)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns an unmodifiable list of the children.
+ * @return An unmodifiable list of children that throws an {@link UnsupportedOperationException}
+ * if an attempt is made to modify it
+ */
+ public List<Markup> getNestedMarkups() {
+ return Collections.unmodifiableList(mNestedMarkups);
+ }
+
+ /**
+ * @hide
+ */
+ public Markup(Parcel in) {
+ mType = in.readString();
+ mPlainText = in.readString();
+ mParameters = in.readBundle();
+ in.readList(mNestedMarkups, Markup.class.getClassLoader());
+ }
+
+ /**
+ * Creates a deep copy of the given markup.
+ */
+ public Markup(Markup markup) {
+ mType = markup.mType;
+ mPlainText = markup.mPlainText;
+ mParameters = markup.mParameters;
+ for (Markup nested : markup.getNestedMarkups()) {
+ addNestedMarkup(new Markup(nested));
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @hide
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mType);
+ dest.writeString(mPlainText);
+ dest.writeBundle(mParameters);
+ dest.writeList(mNestedMarkups);
+ }
+
+ /**
+ * @hide
+ */
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public Markup createFromParcel(Parcel in) {
+ return new Markup(in);
+ }
+
+ public Markup[] newArray(int size) {
+ return new Markup[size];
+ }
+ };
+}
+
diff --git a/core/java/android/speech/tts/SynthesisRequestV2.java b/core/java/android/speech/tts/SynthesisRequestV2.java
index a1da49c..130e3f9 100644
--- a/core/java/android/speech/tts/SynthesisRequestV2.java
+++ b/core/java/android/speech/tts/SynthesisRequestV2.java
@@ -4,11 +4,12 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.speech.tts.TextToSpeechClient.UtteranceId;
+import android.util.Log;
/**
* Service-side representation of a synthesis request from a V2 API client. Contains:
* <ul>
- * <li>The utterance to synthesize</li>
+ * <li>The markup object to synthesize containing the utterance.</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>
@@ -16,8 +17,11 @@ import android.speech.tts.TextToSpeechClient.UtteranceId;
* </ul>
*/
public final class SynthesisRequestV2 implements Parcelable {
- /** Synthesis utterance. */
- private final String mText;
+
+ private static final String TAG = "SynthesisRequestV2";
+
+ /** Synthesis markup */
+ private final Markup mMarkup;
/** Synthesis id. */
private final String mUtteranceId;
@@ -34,9 +38,9 @@ public final class SynthesisRequestV2 implements Parcelable {
/**
* Constructor for test purposes.
*/
- public SynthesisRequestV2(String text, String utteranceId, String voiceName,
+ public SynthesisRequestV2(Markup markup, String utteranceId, String voiceName,
Bundle voiceParams, Bundle audioParams) {
- this.mText = text;
+ this.mMarkup = markup;
this.mUtteranceId = utteranceId;
this.mVoiceName = voiceName;
this.mVoiceParams = voiceParams;
@@ -49,15 +53,18 @@ public final class SynthesisRequestV2 implements Parcelable {
* @hide
*/
public SynthesisRequestV2(Parcel in) {
- this.mText = in.readString();
+ this.mMarkup = (Markup) in.readValue(Markup.class.getClassLoader());
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;
+ /**
+ * Constructor to request the synthesis of a sentence.
+ */
+ SynthesisRequestV2(Markup markup, String utteranceId, RequestConfig rconfig) {
+ this.mMarkup = markup;
this.mUtteranceId = utteranceId;
this.mVoiceName = rconfig.getVoice().getName();
this.mVoiceParams = rconfig.getVoiceParams();
@@ -71,7 +78,7 @@ public final class SynthesisRequestV2 implements Parcelable {
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mText);
+ dest.writeValue(mMarkup);
dest.writeString(mUtteranceId);
dest.writeString(mVoiceName);
dest.writeBundle(mVoiceParams);
@@ -82,7 +89,18 @@ public final class SynthesisRequestV2 implements Parcelable {
* @return the text which should be synthesized.
*/
public String getText() {
- return mText;
+ if (mMarkup.getPlainText() == null) {
+ Log.e(TAG, "Plaintext of markup is null.");
+ return "";
+ }
+ return mMarkup.getPlainText();
+ }
+
+ /**
+ * @return the markup which should be synthesized.
+ */
+ public Markup getMarkup() {
+ return mMarkup;
}
/**
diff --git a/core/java/android/speech/tts/TextToSpeechClient.java b/core/java/android/speech/tts/TextToSpeechClient.java
index 10e2073..0c0be83 100644
--- a/core/java/android/speech/tts/TextToSpeechClient.java
+++ b/core/java/android/speech/tts/TextToSpeechClient.java
@@ -25,7 +25,10 @@ import android.content.ServiceConnection;
import android.media.AudioManager;
import android.net.Uri;
import android.os.AsyncTask;
+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.speech.tts.ITextToSpeechCallback;
@@ -86,6 +89,8 @@ public class TextToSpeechClient {
private HashMap<String, Pair<UtteranceId, RequestCallbacks>> mCallbacks;
// Guarded by mLock
+ private InternalHandler mMainHandler = new InternalHandler();
+
/** Common voices parameters */
public static final class Params {
private Params() {}
@@ -300,6 +305,8 @@ public class TextToSpeechClient {
/**
* Interface definition of callbacks that are called when the client is
* connected or disconnected from the TTS service.
+ *
+ * The callbacks specified in this method will be called on the UI thread.
*/
public static interface ConnectionCallbacks {
/**
@@ -325,6 +332,9 @@ public class TextToSpeechClient {
* 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.
+ *
+ * When the service is working again, the client will receive a callback to the
+ * {@link #onConnectionSuccess()} method.
*/
public void onServiceDisconnected();
@@ -502,7 +512,6 @@ public class TextToSpeechClient {
}
}
-
/**
* Connects the client to TTS service. This method returns immediately, and connects to the
* service in the background.
@@ -688,7 +697,8 @@ public class TextToSpeechClient {
synchronized (mLock) {
mEngineStatus = new EngineStatus(mServiceConnection.getEngineName(),
voicesInfo);
- mConnectionCallbacks.onEngineStatusChange(mEngineStatus);
+ mMainHandler.obtainMessage(InternalHandler.WHAT_ENGINE_STATUS_CHANGED,
+ mEngineStatus).sendToTarget();
}
}
};
@@ -753,9 +763,11 @@ public class TextToSpeechClient {
Log.i(TAG, "Asked to disconnect from " + name);
synchronized(mLock) {
+ mEstablished = false;
+ mService = null;
stopSetupConnectionTask();
}
- mConnectionCallbacks.onServiceDisconnected();
+ mMainHandler.obtainMessage(InternalHandler.WHAT_SERVICE_DISCONNECTED).sendToTarget();
}
private void startSetupConnectionTask(ComponentName name) {
@@ -781,15 +793,14 @@ public class TextToSpeechClient {
return mService != null && mEstablished;
}
- boolean runAction(Action action) {
+ <T> ActionResult<T> runAction(Action<T> action) {
synchronized (mLock) {
try {
- action.run(mService);
- return true;
+ return new ActionResult<T>(true, action.run(mService));
} catch (Exception ex) {
Log.e(TAG, action.getName() + " failed", ex);
disconnect();
- return false;
+ return new ActionResult<T>(false);
}
}
}
@@ -809,7 +820,7 @@ public class TextToSpeechClient {
}
}
- private abstract class Action {
+ private abstract class Action<T> {
private final String mName;
public Action(String name) {
@@ -817,7 +828,21 @@ public class TextToSpeechClient {
}
public String getName() {return mName;}
- abstract void run(ITextToSpeechService service) throws RemoteException;
+ abstract T run(ITextToSpeechService service) throws RemoteException;
+ }
+
+ private class ActionResult<T> {
+ boolean mSuccess;
+ T mResult;
+
+ ActionResult(boolean success) {
+ mSuccess = success;
+ }
+
+ ActionResult(boolean success, T result) {
+ mSuccess = success;
+ mResult = result;
+ }
}
private IBinder getCallerIdentity() {
@@ -827,16 +852,17 @@ public class TextToSpeechClient {
return null;
}
- private boolean runAction(Action action) {
+ private <T> ActionResult<T> runAction(Action<T> action) {
synchronized (mLock) {
if (mServiceConnection == null) {
- return false;
+ Log.w(TAG, action.getName() + " failed: not bound to TTS engine");
+ return new ActionResult<T>(false);
}
if (!mServiceConnection.isEstablished()) {
- return false;
+ Log.w(TAG, action.getName() + " failed: not fully bound to TTS engine");
+ return new ActionResult<T>(false);
}
- mServiceConnection.runAction(action);
- return true;
+ return mServiceConnection.runAction(action);
}
}
@@ -847,13 +873,14 @@ public class TextToSpeechClient {
* other utterances in the queue.
*/
public void stop() {
- runAction(new Action(ACTION_STOP_NAME) {
+ runAction(new Action<Void>(ACTION_STOP_NAME) {
@Override
- public void run(ITextToSpeechService service) throws RemoteException {
+ public Void run(ITextToSpeechService service) throws RemoteException {
if (service.stop(getCallerIdentity()) != Status.SUCCESS) {
Log.e(TAG, "Stop failed");
}
mCallbacks.clear();
+ return null;
}
});
}
@@ -861,7 +888,7 @@ public class TextToSpeechClient {
private static final String ACTION_QUEUE_SPEAK_NAME = "queueSpeak";
/**
- * Speaks the string using the specified queuing strategy using current
+ * Speaks the string using the specified queuing strategy and the 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.
@@ -872,15 +899,38 @@ public class TextToSpeechClient {
* 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
+ * @param callbacks Synthesis request callbacks. If null, the 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) {
+ queueSpeak(createMarkupFromString(utterance), utteranceId, config, callbacks);
+ }
+
+ /**
+ * Speaks the {@link Markup} (which can be constructed with {@link Utterance}) using
+ * the specified queuing strategy and the 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 markup The Markup to be spoken. The written equivalent of the spoken
+ * text should be 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, the default request
+ * callbacks object will be used.
+ */
+ public void queueSpeak(final Markup markup,
+ final UtteranceId utteranceId,
+ final RequestConfig config,
+ final RequestCallbacks callbacks) {
+ runAction(new Action<Void>(ACTION_QUEUE_SPEAK_NAME) {
@Override
- public void run(ITextToSpeechService service) throws RemoteException {
+ public Void run(ITextToSpeechService service) throws RemoteException {
RequestCallbacks c = mDefaultRequestCallbacks;
if (callbacks != null) {
c = callbacks;
@@ -888,15 +938,16 @@ public class TextToSpeechClient {
int addCallbackStatus = addCallback(utteranceId, c);
if (addCallbackStatus != Status.SUCCESS) {
c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST);
- return;
+ return null;
}
int queueResult = service.speakV2(
getCallerIdentity(),
- new SynthesisRequestV2(utterance, utteranceId.toUniqueString(), config));
+ new SynthesisRequestV2(markup, utteranceId.toUniqueString(), config));
if (queueResult != Status.SUCCESS) {
removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
}
+ return null;
}
});
}
@@ -916,15 +967,40 @@ public class TextToSpeechClient {
* @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
+ * @param callbacks Synthesis request callbacks. If null, the 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) {
+ queueSynthesizeToFile(createMarkupFromString(utterance), utteranceId, outputFile, config, callbacks);
+ }
+
+ /**
+ * Synthesizes the given {@link Markup} (can be constructed with {@link Utterance})
+ * 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 markup The Markup that should be synthesized. The written equivalent of
+ * the spoken text should be 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, the default request
+ * callbacks object will be used.
+ */
+ public void queueSynthesizeToFile(
+ final Markup markup,
+ final UtteranceId utteranceId,
+ final File outputFile, final RequestConfig config,
+ final RequestCallbacks callbacks) {
+ runAction(new Action<Void>(ACTION_QUEUE_SYNTHESIZE_TO_FILE) {
@Override
- public void run(ITextToSpeechService service) throws RemoteException {
+ public Void run(ITextToSpeechService service) throws RemoteException {
RequestCallbacks c = mDefaultRequestCallbacks;
if (callbacks != null) {
c = callbacks;
@@ -932,7 +1008,7 @@ public class TextToSpeechClient {
int addCallbackStatus = addCallback(utteranceId, c);
if (addCallbackStatus != Status.SUCCESS) {
c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST);
- return;
+ return null;
}
ParcelFileDescriptor fileDescriptor = null;
@@ -940,7 +1016,7 @@ public class TextToSpeechClient {
if (outputFile.exists() && !outputFile.canWrite()) {
Log.e(TAG, "No permissions to write to " + outputFile);
removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT);
- return;
+ return null;
}
fileDescriptor = ParcelFileDescriptor.open(outputFile,
ParcelFileDescriptor.MODE_WRITE_ONLY |
@@ -949,8 +1025,7 @@ public class TextToSpeechClient {
int queueResult = service.synthesizeToFileDescriptorV2(getCallerIdentity(),
fileDescriptor,
- new SynthesisRequestV2(utterance, utteranceId.toUniqueString(),
- config));
+ new SynthesisRequestV2(markup, utteranceId.toUniqueString(), config));
fileDescriptor.close();
if (queueResult != Status.SUCCESS) {
removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
@@ -962,10 +1037,18 @@ public class TextToSpeechClient {
Log.e(TAG, "Closing file " + outputFile + " failed", e);
removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT);
}
+ return null;
}
});
}
+ private static Markup createMarkupFromString(String str) {
+ return new Utterance()
+ .append(new Utterance.TtsText(str))
+ .setNoWarningOnFallback(true)
+ .createMarkup();
+ }
+
private static final String ACTION_QUEUE_SILENCE_NAME = "queueSilence";
/**
@@ -982,9 +1065,9 @@ public class TextToSpeechClient {
*/
public void queueSilence(final long durationInMs, final UtteranceId utteranceId,
final RequestCallbacks callbacks) {
- runAction(new Action(ACTION_QUEUE_SILENCE_NAME) {
+ runAction(new Action<Void>(ACTION_QUEUE_SILENCE_NAME) {
@Override
- public void run(ITextToSpeechService service) throws RemoteException {
+ public Void run(ITextToSpeechService service) throws RemoteException {
RequestCallbacks c = mDefaultRequestCallbacks;
if (callbacks != null) {
c = callbacks;
@@ -1000,6 +1083,7 @@ public class TextToSpeechClient {
if (queueResult != Status.SUCCESS) {
removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
}
+ return null;
}
});
}
@@ -1023,9 +1107,9 @@ public class TextToSpeechClient {
*/
public void queueAudio(final Uri audioUrl, final UtteranceId utteranceId,
final RequestConfig config, final RequestCallbacks callbacks) {
- runAction(new Action(ACTION_QUEUE_AUDIO_NAME) {
+ runAction(new Action<Void>(ACTION_QUEUE_AUDIO_NAME) {
@Override
- public void run(ITextToSpeechService service) throws RemoteException {
+ public Void run(ITextToSpeechService service) throws RemoteException {
RequestCallbacks c = mDefaultRequestCallbacks;
if (callbacks != null) {
c = callbacks;
@@ -1041,7 +1125,49 @@ public class TextToSpeechClient {
if (queueResult != Status.SUCCESS) {
removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
}
+ return null;
}
});
}
+
+ private static final String ACTION_IS_SPEAKING_NAME = "isSpeaking";
+
+ /**
+ * Checks whether the TTS engine is busy speaking. Note that a speech item is
+ * considered complete once it's audio data has been sent to the audio mixer, or
+ * written to a file. There might be a finite lag between this point, and when
+ * the audio hardware completes playback.
+ *
+ * @return {@code true} if the TTS engine is speaking.
+ */
+ public boolean isSpeaking() {
+ ActionResult<Boolean> result = runAction(new Action<Boolean>(ACTION_IS_SPEAKING_NAME) {
+ @Override
+ public Boolean run(ITextToSpeechService service) throws RemoteException {
+ return service.isSpeaking();
+ }
+ });
+ if (!result.mSuccess) {
+ return false; // We can't really say, return false
+ }
+ return result.mResult;
+ }
+
+
+ class InternalHandler extends Handler {
+ final static int WHAT_ENGINE_STATUS_CHANGED = 1;
+ final static int WHAT_SERVICE_DISCONNECTED = 2;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case WHAT_ENGINE_STATUS_CHANGED:
+ mConnectionCallbacks.onEngineStatusChange((EngineStatus) msg.obj);
+ return;
+ case WHAT_SERVICE_DISCONNECTED:
+ mConnectionCallbacks.onServiceDisconnected();
+ return;
+ }
+ }
+ }
}
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index d7c51fc..14a4024 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -255,7 +255,8 @@ public abstract class TextToSpeechService extends Service {
// 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);
+ // Speech speed <= 0 makes it use a system wide setting
+ defaultParams.putFloat(TextToSpeechClient.Params.SPEECH_SPEED, 0.0f);
// Enumerate all locales and check if they are available
ArrayList<VoiceInfo> voicesInfo = new ArrayList<VoiceInfo>();
@@ -351,6 +352,12 @@ public abstract class TextToSpeechService extends Service {
params.putString(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS, "true");
}
+ String noWarning = request.getMarkup().getParameter(Utterance.KEY_NO_WARNING_ON_FALLBACK);
+ if (noWarning == null || noWarning.equals("false")) {
+ Log.w("TextToSpeechService", "The synthesis engine does not support Markup, falling " +
+ "back to the given plain text.");
+ }
+
// Build V1 request
SynthesisRequest requestV1 = new SynthesisRequest(request.getText(), params);
Locale locale = selectedVoice.getLocale();
@@ -855,14 +862,53 @@ public abstract class TextToSpeechService extends Service {
}
}
+ /**
+ * Estimate of the character count equivalent of a Markup instance. Calculated
+ * by summing the characters of all Markups of type "text". Each other node
+ * is counted as a single character, as the character count of other nodes
+ * is non-trivial to calculate and we don't want to accept arbitrarily large
+ * requests.
+ */
+ private int estimateSynthesisLengthFromMarkup(Markup m) {
+ int size = 0;
+ if (m.getType() != null &&
+ m.getType().equals("text") &&
+ m.getParameter("text") != null) {
+ size += m.getParameter("text").length();
+ } else if (m.getType() == null ||
+ !m.getType().equals("utterance")) {
+ size += 1;
+ }
+ for (Markup nested : m.getNestedMarkups()) {
+ size += estimateSynthesisLengthFromMarkup(nested);
+ }
+ return size;
+ }
+
@Override
public boolean isValid() {
- if (mSynthesisRequest.getText() == null) {
- Log.e(TAG, "null synthesis text");
+ if (mSynthesisRequest.getMarkup() == null) {
+ Log.e(TAG, "No markup in request.");
return false;
}
- if (mSynthesisRequest.getText().length() >= TextToSpeech.getMaxSpeechInputLength()) {
- Log.w(TAG, "Text too long: " + mSynthesisRequest.getText().length() + " chars");
+ String type = mSynthesisRequest.getMarkup().getType();
+ if (type == null) {
+ Log.w(TAG, "Top level markup node should have type \"utterance\", not null");
+ return false;
+ } else if (!type.equals("utterance")) {
+ Log.w(TAG, "Top level markup node should have type \"utterance\" instead of " +
+ "\"" + type + "\"");
+ return false;
+ }
+
+ int estimate = estimateSynthesisLengthFromMarkup(mSynthesisRequest.getMarkup());
+ if (estimate >= TextToSpeech.getMaxSpeechInputLength()) {
+ Log.w(TAG, "Text too long: estimated size of text was " + estimate + " chars.");
+ return false;
+ }
+
+ if (estimate <= 0) {
+ Log.e(TAG, "null synthesis text");
return false;
}
diff --git a/core/java/android/speech/tts/TtsEngines.java b/core/java/android/speech/tts/TtsEngines.java
index 4f996cd..9b929a3 100644
--- a/core/java/android/speech/tts/TtsEngines.java
+++ b/core/java/android/speech/tts/TtsEngines.java
@@ -307,6 +307,24 @@ public class TtsEngines {
}
/**
+ * True if a given TTS engine uses the default phone locale as a default locale. Attempts to
+ * read the value from {@link Settings.Secure#TTS_DEFAULT_LOCALE}, failing which the
+ * old style value from {@link Settings.Secure#TTS_DEFAULT_LANG} is read. If
+ * both these values are empty, this methods returns true.
+ *
+ * @param engineName the engine to return the locale for.
+ */
+ public boolean isLocaleSetToDefaultForEngine(String engineName) {
+ return (TextUtils.isEmpty(parseEnginePrefFromList(
+ getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE),
+ engineName)) &&
+ TextUtils.isEmpty(
+ Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.TTS_DEFAULT_LANG)));
+ }
+
+
+ /**
* Parses a locale preference value delimited by {@link #LOCALE_DELIMITER}.
* Varies from {@link String#split} in that it will always return an array
* of length 3 with non null values.
diff --git a/core/java/android/speech/tts/Utterance.java b/core/java/android/speech/tts/Utterance.java
new file mode 100644
index 0000000..0a29283
--- /dev/null
+++ b/core/java/android/speech/tts/Utterance.java
@@ -0,0 +1,595 @@
+package android.speech.tts;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class acts as a builder for {@link Markup} instances.
+ * <p>
+ * Each Utterance consists of a list of the semiotic classes ({@link Utterance.TtsCardinal} and
+ * {@link Utterance.TtsText}).
+ * <p>Each semiotic class can be supplied with morphosyntactic features
+ * (gender, animacy, multiplicity and case), it is up to the synthesis engine to use this
+ * information during synthesis.
+ * Examples where morphosyntactic features matter:
+ * <ul>
+ * <li>In French, the number one is verbalized differently based on the gender of the noun
+ * it modifies. "un homme" (one man) versus "une femme" (one woman).
+ * <li>In German the grammatical case (accusative, locative, etc) needs to be included to be
+ * verbalize correctly. In German you'd have the sentence "Sie haben 1 kilometer vor Ihnen" (You
+ * have 1 kilometer ahead of you), "1" in this case needs to become inflected to the accusative
+ * form ("einen") instead of the nominative form "ein".
+ * </p>
+ * <p>
+ * Utterance usage example:
+ * Markup m1 = new Utterance().append("The Eiffel Tower is")
+ * .append(new TtsCardinal(324))
+ * .append("meters tall.");
+ * Markup m2 = new Utterance().append("Sie haben")
+ * .append(new TtsCardinal(1).setGender(Utterance.GENDER_MALE)
+ * .append("Tag frei.");
+ * </p>
+ */
+public class Utterance {
+
+ /***
+ * Toplevel type of markup representation.
+ */
+ public static final String TYPE_UTTERANCE = "utterance";
+ /***
+ * The no_warning_on_fallback parameter can be set to "false" or "true", true indicating that
+ * no warning will be given when the synthesizer does not support Markup. This is used when
+ * the user only provides a string to the API instead of a markup.
+ */
+ public static final String KEY_NO_WARNING_ON_FALLBACK = "no_warning_on_fallback";
+
+ // Gender.
+ public final static int GENDER_UNKNOWN = 0;
+ public final static int GENDER_NEUTRAL = 1;
+ public final static int GENDER_MALE = 2;
+ public final static int GENDER_FEMALE = 3;
+
+ // Animacy.
+ public final static int ANIMACY_UNKNOWN = 0;
+ public final static int ANIMACY_ANIMATE = 1;
+ public final static int ANIMACY_INANIMATE = 2;
+
+ // Multiplicity.
+ public final static int MULTIPLICITY_UNKNOWN = 0;
+ public final static int MULTIPLICITY_SINGLE = 1;
+ public final static int MULTIPLICITY_DUAL = 2;
+ public final static int MULTIPLICITY_PLURAL = 3;
+
+ // Case.
+ public final static int CASE_UNKNOWN = 0;
+ public final static int CASE_NOMINATIVE = 1;
+ public final static int CASE_ACCUSATIVE = 2;
+ public final static int CASE_DATIVE = 3;
+ public final static int CASE_ABLATIVE = 4;
+ public final static int CASE_GENITIVE = 5;
+ public final static int CASE_VOCATIVE = 6;
+ public final static int CASE_LOCATIVE = 7;
+ public final static int CASE_INSTRUMENTAL = 8;
+
+ private List<AbstractTts<? extends AbstractTts<?>>> says =
+ new ArrayList<AbstractTts<? extends AbstractTts<?>>>();
+ Boolean mNoWarningOnFallback = null;
+
+ /**
+ * Objects deriving from this class can be appended to a Utterance. This class uses generics
+ * so method from this class can return instances of its child classes, resulting in a better
+ * API (CRTP pattern).
+ */
+ public static abstract class AbstractTts<C extends AbstractTts<C>> {
+
+ protected Markup mMarkup = new Markup();
+
+ /**
+ * Empty constructor.
+ */
+ protected AbstractTts() {
+ }
+
+ /**
+ * Construct with Markup.
+ * @param markup
+ */
+ protected AbstractTts(Markup markup) {
+ mMarkup = markup;
+ }
+
+ /**
+ * Returns the type of this class, e.g. "cardinal" or "measure".
+ * @return The type.
+ */
+ public String getType() {
+ return mMarkup.getType();
+ }
+
+ /**
+ * A fallback plain text can be provided, in case the engine does not support this class
+ * type, or even Markup altogether.
+ * @param plainText A string with the plain text.
+ * @return This instance.
+ */
+ @SuppressWarnings("unchecked")
+ public C setPlainText(String plainText) {
+ mMarkup.setPlainText(plainText);
+ return (C) this;
+ }
+
+ /**
+ * Returns the plain text (fallback) string.
+ * @return Plain text string or null if not set.
+ */
+ public String getPlainText() {
+ return mMarkup.getPlainText();
+ }
+
+ /**
+ * Populates the plainText if not set and builds a Markup instance.
+ * @return The Markup object describing this instance.
+ */
+ public Markup getMarkup() {
+ return new Markup(mMarkup);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected C setParameter(String key, String value) {
+ mMarkup.setParameter(key, value);
+ return (C) this;
+ }
+
+ protected String getParameter(String key) {
+ return mMarkup.getParameter(key);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected C removeParameter(String key) {
+ mMarkup.removeParameter(key);
+ return (C) this;
+ }
+
+ /**
+ * Returns a string representation of this instance, can be deserialized to an equal
+ * Utterance instance.
+ */
+ public String toString() {
+ return mMarkup.toString();
+ }
+
+ /**
+ * Returns a generated plain text alternative for this instance if this instance isn't
+ * better representated by the list of it's children.
+ * @return Best effort plain text representation of this instance, can be null.
+ */
+ public String generatePlainText() {
+ return null;
+ }
+ }
+
+ public static abstract class AbstractTtsSemioticClass<C extends AbstractTtsSemioticClass<C>>
+ extends AbstractTts<C> {
+ // Keys.
+ private static final String KEY_GENDER = "gender";
+ private static final String KEY_ANIMACY = "animacy";
+ private static final String KEY_MULTIPLICITY = "multiplicity";
+ private static final String KEY_CASE = "case";
+
+ protected AbstractTtsSemioticClass() {
+ super();
+ }
+
+ protected AbstractTtsSemioticClass(Markup markup) {
+ super(markup);
+ }
+
+ @SuppressWarnings("unchecked")
+ public C setGender(int gender) {
+ if (gender < 0 || gender > 3) {
+ throw new IllegalArgumentException("Only four types of gender can be set: " +
+ "unknown, neutral, maculine and female.");
+ }
+ if (gender != GENDER_UNKNOWN) {
+ setParameter(KEY_GENDER, String.valueOf(gender));
+ } else {
+ setParameter(KEY_GENDER, null);
+ }
+ return (C) this;
+ }
+
+ public int getGender() {
+ String gender = mMarkup.getParameter(KEY_GENDER);
+ return gender != null ? Integer.valueOf(gender) : GENDER_UNKNOWN;
+ }
+
+ @SuppressWarnings("unchecked")
+ public C setAnimacy(int animacy) {
+ if (animacy < 0 || animacy > 2) {
+ throw new IllegalArgumentException(
+ "Only two types of animacy can be set: unknown, animate and inanimate");
+ }
+ if (animacy != ANIMACY_UNKNOWN) {
+ setParameter(KEY_ANIMACY, String.valueOf(animacy));
+ } else {
+ setParameter(KEY_ANIMACY, null);
+ }
+ return (C) this;
+ }
+
+ public int getAnimacy() {
+ String animacy = getParameter(KEY_ANIMACY);
+ return animacy != null ? Integer.valueOf(animacy) : ANIMACY_UNKNOWN;
+ }
+
+ @SuppressWarnings("unchecked")
+ public C setMultiplicity(int multiplicity) {
+ if (multiplicity < 0 || multiplicity > 3) {
+ throw new IllegalArgumentException(
+ "Only four types of multiplicity can be set: unknown, single, dual and " +
+ "plural.");
+ }
+ if (multiplicity != MULTIPLICITY_UNKNOWN) {
+ setParameter(KEY_MULTIPLICITY, String.valueOf(multiplicity));
+ } else {
+ setParameter(KEY_MULTIPLICITY, null);
+ }
+ return (C) this;
+ }
+
+ public int getMultiplicity() {
+ String multiplicity = mMarkup.getParameter(KEY_MULTIPLICITY);
+ return multiplicity != null ? Integer.valueOf(multiplicity) : MULTIPLICITY_UNKNOWN;
+ }
+
+ @SuppressWarnings("unchecked")
+ public C setCase(int grammaticalCase) {
+ if (grammaticalCase < 0 || grammaticalCase > 8) {
+ throw new IllegalArgumentException(
+ "Only nine types of grammatical case can be set.");
+ }
+ if (grammaticalCase != CASE_UNKNOWN) {
+ setParameter(KEY_CASE, String.valueOf(grammaticalCase));
+ } else {
+ setParameter(KEY_CASE, null);
+ }
+ return (C) this;
+ }
+
+ public int getCase() {
+ String grammaticalCase = mMarkup.getParameter(KEY_CASE);
+ return grammaticalCase != null ? Integer.valueOf(grammaticalCase) : CASE_UNKNOWN;
+ }
+ }
+
+ /**
+ * Class that contains regular text, synthesis engine pronounces it using its regular pipeline.
+ * Parameters:
+ * <ul>
+ * <li>Text: the text to synthesize</li>
+ * </ul>
+ */
+ public static class TtsText extends AbstractTtsSemioticClass<TtsText> {
+
+ // The type of this node.
+ protected static final String TYPE_TEXT = "text";
+ // The text parameter stores the text to be synthesized.
+ private static final String KEY_TEXT = "text";
+
+ /**
+ * Default constructor.
+ */
+ public TtsText() {
+ mMarkup.setType(TYPE_TEXT);
+ }
+
+ /**
+ * Constructor that sets the text to be synthesized.
+ * @param text The text to be synthesized.
+ */
+ public TtsText(String text) {
+ this();
+ setText(text);
+ }
+
+ /**
+ * Constructs a TtsText with the values of the Markup, does not check if the given Markup is
+ * of the right type.
+ */
+ private TtsText(Markup markup) {
+ super(markup);
+ }
+
+ /**
+ * Sets the text to be synthesized.
+ * @return This instance.
+ */
+ public TtsText setText(String text) {
+ setParameter(KEY_TEXT, text);
+ return this;
+ }
+
+ /**
+ * Returns the text to be synthesized.
+ * @return This instance.
+ */
+ public String getText() {
+ return getParameter(KEY_TEXT);
+ }
+
+ /**
+ * Generates a best effort plain text, in this case simply the text.
+ */
+ @Override
+ public String generatePlainText() {
+ return getText();
+ }
+ }
+
+ /**
+ * Contains a cardinal.
+ * Parameters:
+ * <ul>
+ * <li>integer: the integer to synthesize</li>
+ * </ul>
+ */
+ public static class TtsCardinal extends AbstractTtsSemioticClass<TtsCardinal> {
+
+ // The type of this node.
+ protected static final String TYPE_CARDINAL = "cardinal";
+ // The parameter integer stores the integer to synthesize.
+ private static final String KEY_INTEGER = "integer";
+
+ /**
+ * Default constructor.
+ */
+ public TtsCardinal() {
+ mMarkup.setType(TYPE_CARDINAL);
+ }
+
+ /**
+ * Constructor that sets the integer to be synthesized.
+ */
+ public TtsCardinal(int integer) {
+ this();
+ setInteger(integer);
+ }
+
+ /**
+ * Constructor that sets the integer to be synthesized.
+ */
+ public TtsCardinal(String integer) {
+ this();
+ setInteger(integer);
+ }
+
+ /**
+ * Constructs a TtsText with the values of the Markup.
+ * Does not check if the given Markup is of the right type.
+ */
+ private TtsCardinal(Markup markup) {
+ super(markup);
+ }
+
+ /**
+ * Sets the integer.
+ * @return This instance.
+ */
+ public TtsCardinal setInteger(int integer) {
+ return setInteger(String.valueOf(integer));
+ }
+
+ /**
+ * Sets the integer.
+ * @param integer A non-empty string of digits with an optional '-' in front.
+ * @return This instance.
+ */
+ public TtsCardinal setInteger(String integer) {
+ if (!integer.matches("-?\\d+")) {
+ throw new IllegalArgumentException("Expected a cardinal: \"" + integer + "\"");
+ }
+ setParameter(KEY_INTEGER, integer);
+ return this;
+ }
+
+ /**
+ * Returns the integer parameter.
+ */
+ public String getInteger() {
+ return getParameter(KEY_INTEGER);
+ }
+
+ /**
+ * Generates a best effort plain text, in this case simply the integer.
+ */
+ @Override
+ public String generatePlainText() {
+ return getInteger();
+ }
+ }
+
+ /**
+ * Default constructor.
+ */
+ public Utterance() {}
+
+ /**
+ * Returns the plain text of a given Markup if it was set; if it's not set, recursively call the
+ * this same method on its children.
+ */
+ private String constructPlainText(Markup m) {
+ StringBuilder plainText = new StringBuilder();
+ if (m.getPlainText() != null) {
+ plainText.append(m.getPlainText());
+ } else {
+ for (Markup nestedMarkup : m.getNestedMarkups()) {
+ String nestedPlainText = constructPlainText(nestedMarkup);
+ if (!nestedPlainText.isEmpty()) {
+ if (plainText.length() != 0) {
+ plainText.append(" ");
+ }
+ plainText.append(nestedPlainText);
+ }
+ }
+ }
+ return plainText.toString();
+ }
+
+ /**
+ * Creates a Markup instance with auto generated plain texts for the relevant nodes, in case the
+ * user has not provided one already.
+ * @return A Markup instance representing this utterance.
+ */
+ public Markup createMarkup() {
+ Markup markup = new Markup(TYPE_UTTERANCE);
+ StringBuilder plainText = new StringBuilder();
+ for (AbstractTts<? extends AbstractTts<?>> say : says) {
+ // Get a copy of this markup, and generate a plaintext for it if is not set.
+ Markup sayMarkup = say.getMarkup();
+ if (sayMarkup.getPlainText() == null) {
+ sayMarkup.setPlainText(say.generatePlainText());
+ }
+ if (plainText.length() != 0) {
+ plainText.append(" ");
+ }
+ plainText.append(constructPlainText(sayMarkup));
+ markup.addNestedMarkup(sayMarkup);
+ }
+ if (mNoWarningOnFallback != null) {
+ markup.setParameter(KEY_NO_WARNING_ON_FALLBACK,
+ mNoWarningOnFallback ? "true" : "false");
+ }
+ markup.setPlainText(plainText.toString());
+ return markup;
+ }
+
+ /**
+ * Appends an element to this Utterance instance.
+ * @return this instance
+ */
+ public Utterance append(AbstractTts<? extends AbstractTts<?>> say) {
+ says.add(say);
+ return this;
+ }
+
+ private Utterance append(Markup markup) {
+ if (markup.getType().equals(TtsText.TYPE_TEXT)) {
+ append(new TtsText(markup));
+ } else if (markup.getType().equals(TtsCardinal.TYPE_CARDINAL)) {
+ append(new TtsCardinal(markup));
+ } else {
+ // Unknown node, a class we don't know about.
+ if (markup.getPlainText() != null) {
+ append(new TtsText(markup.getPlainText()));
+ } else {
+ // No plainText specified; add its children
+ // seperately. In case of a new prosody node,
+ // we would still verbalize it correctly.
+ for (Markup nested : markup.getNestedMarkups()) {
+ append(nested);
+ }
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Returns a string representation of this Utterance instance. Can be deserialized back to an
+ * Utterance instance with utteranceFromString(). Can be used to store utterances to be used
+ * at a later time.
+ */
+ public String toString() {
+ String out = "type: \"" + TYPE_UTTERANCE + "\"";
+ if (mNoWarningOnFallback != null) {
+ out += " no_warning_on_fallback: \"" + (mNoWarningOnFallback ? "true" : "false") + "\"";
+ }
+ for (AbstractTts<? extends AbstractTts<?>> say : says) {
+ out += " markup { " + say.getMarkup().toString() + " }";
+ }
+ return out;
+ }
+
+ /**
+ * Returns an Utterance instance from the string representation generated by toString().
+ * @param string The string representation generated by toString().
+ * @return The new Utterance instance.
+ * @throws {@link IllegalArgumentException} if the input cannot be correctly parsed.
+ */
+ static public Utterance utteranceFromString(String string) throws IllegalArgumentException {
+ Utterance utterance = new Utterance();
+ Markup markup = Markup.markupFromString(string);
+ if (!markup.getType().equals(TYPE_UTTERANCE)) {
+ throw new IllegalArgumentException("Top level markup should be of type \"" +
+ TYPE_UTTERANCE + "\", but was of type \"" +
+ markup.getType() + "\".") ;
+ }
+ for (Markup nestedMarkup : markup.getNestedMarkups()) {
+ utterance.append(nestedMarkup);
+ }
+ return utterance;
+ }
+
+ /**
+ * Appends a new TtsText with the given text.
+ * @param text The text to synthesize.
+ * @return This instance.
+ */
+ public Utterance append(String text) {
+ return append(new TtsText(text));
+ }
+
+ /**
+ * Appends a TtsCardinal representing the given number.
+ * @param integer The integer to synthesize.
+ * @return this
+ */
+ public Utterance append(int integer) {
+ return append(new TtsCardinal(integer));
+ }
+
+ /**
+ * Returns the n'th element in this Utterance.
+ * @param i The index.
+ * @return The n'th element in this Utterance.
+ * @throws {@link IndexOutOfBoundsException} - if i < 0 || i >= size()
+ */
+ public AbstractTts<? extends AbstractTts<?>> get(int i) {
+ return says.get(i);
+ }
+
+ /**
+ * Returns the number of elements in this Utterance.
+ * @return The number of elements in this Utterance.
+ */
+ public int size() {
+ return says.size();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( this == o ) return true;
+ if ( !(o instanceof Utterance) ) return false;
+ Utterance utt = (Utterance) o;
+
+ if (says.size() != utt.says.size()) {
+ return false;
+ }
+
+ for (int i = 0; i < says.size(); i++) {
+ if (!says.get(i).getMarkup().equals(utt.says.get(i).getMarkup())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Can be set to true or false, true indicating that the user provided only a string to the API,
+ * at which the system will not issue a warning if the synthesizer falls back onto the plain
+ * text when the synthesizer does not support Markup.
+ */
+ public Utterance setNoWarningOnFallback(boolean noWarning) {
+ mNoWarningOnFallback = noWarning;
+ return this;
+ }
+}
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
index 0bd46bc..b17f502 100644
--- a/core/java/android/text/method/QwertyKeyListener.java
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -115,7 +115,7 @@ public class QwertyKeyListener extends BaseKeyListener {
if (count > 0 && selStart == selEnd && selStart > 0) {
char c = content.charAt(selStart - 1);
- if (c == i || c == Character.toUpperCase(i) && view != null) {
+ if ((c == i || c == Character.toUpperCase(i)) && view != null) {
if (showCharacterPicker(view, content, c, false, count)) {
resetMetaState(content);
return true;
diff --git a/core/java/android/transition/ChangeTransform.java b/core/java/android/transition/ChangeTransform.java
index 85cb2c7..8c03e82 100644
--- a/core/java/android/transition/ChangeTransform.java
+++ b/core/java/android/transition/ChangeTransform.java
@@ -69,7 +69,7 @@ public class ChangeTransform extends Transition {
public void set(View view, float[] values) {
for (int i = 0; i < values.length; i++) {
float value = values[i];
- if (value != Float.NaN) {
+ if (!Float.isNaN(value)) {
sChangedProperties[i].setValue(view, value);
}
}
diff --git a/core/java/android/transition/MoveImage.java b/core/java/android/transition/MoveImage.java
index 183cdd2..6f1b6f7 100644
--- a/core/java/android/transition/MoveImage.java
+++ b/core/java/android/transition/MoveImage.java
@@ -64,7 +64,13 @@ public class MoveImage extends Transition {
if (!(view instanceof ImageView) || view.getVisibility() != View.VISIBLE) {
return;
}
+ ImageView imageView = (ImageView) view;
+ Drawable drawable = imageView.getDrawable();
+ if (drawable == null) {
+ return;
+ }
Map<String, Object> values = transitionValues.values;
+ values.put(PROPNAME_DRAWABLE, drawable);
ViewGroup parent = (ViewGroup) view.getParent();
parent.getLocationInWindow(mTempLoc);
@@ -79,11 +85,9 @@ public class MoveImage extends Transition {
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
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index 49a0138..9a70099 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -89,7 +89,8 @@ import java.util.List;
* 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>, <code>targetClass</code>,
- * <code>excludeId</code>, or <code>excludeClass</code>, which this transition acts upon.
+ * <code>targetViewName</code>, <code>excludeId</code>, <code>excludeClass</code>, or
+ * <code>excludeViewName</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
@@ -106,6 +107,40 @@ public abstract class Transition implements Cloneable {
private static final String LOG_TAG = "Transition";
static final boolean DBG = false;
+ /**
+ * With {@link #setMatchOrder(int...)}, chooses to match by View instance.
+ */
+ public static final int MATCH_INSTANCE = 0x1;
+ private static final int MATCH_FIRST = MATCH_INSTANCE;
+
+ /**
+ * With {@link #setMatchOrder(int...)}, chooses to match by
+ * {@link android.view.View#getViewName()}. Null names will not be matched.
+ */
+ public static final int MATCH_VIEW_NAME = 0x2;
+
+ /**
+ * With {@link #setMatchOrder(int...)}, chooses to match by
+ * {@link android.view.View#getId()}. Negative IDs will not be matched.
+ */
+ public static final int MATCH_ID = 0x3;
+
+ /**
+ * With {@link #setMatchOrder(int...)}, chooses to match by the {@link android.widget.Adapter}
+ * item id. When {@link android.widget.Adapter#hasStableIds()} returns false, no match
+ * will be made for items.
+ */
+ public static final int MATCH_ITEM_ID = 0x4;
+
+ private static final int MATCH_LAST = MATCH_ITEM_ID;
+
+ private static final int[] DEFAULT_MATCH_ORDER = {
+ MATCH_VIEW_NAME,
+ MATCH_INSTANCE,
+ MATCH_ID,
+ MATCH_ITEM_ID,
+ };
+
private String mName = getClass().getName();
long mStartDelay = -1;
@@ -113,16 +148,19 @@ public abstract class Transition implements Cloneable {
TimeInterpolator mInterpolator = null;
ArrayList<Integer> mTargetIds = new ArrayList<Integer>();
ArrayList<View> mTargets = new ArrayList<View>();
+ ArrayList<String> mTargetNames = null;
+ ArrayList<Class> mTargetTypes = null;
ArrayList<Integer> mTargetIdExcludes = null;
ArrayList<View> mTargetExcludes = null;
ArrayList<Class> mTargetTypeExcludes = null;
- ArrayList<Class> mTargetTypes = null;
+ ArrayList<String> mTargetNameExcludes = null;
ArrayList<Integer> mTargetIdChildExcludes = null;
ArrayList<View> mTargetChildExcludes = null;
ArrayList<Class> mTargetTypeChildExcludes = null;
private TransitionValuesMaps mStartValues = new TransitionValuesMaps();
private TransitionValuesMaps mEndValues = new TransitionValuesMaps();
TransitionSet mParent = null;
+ private int[] mMatchOrder = DEFAULT_MATCH_ORDER;
// Per-animator information used for later canceling when future transitions overlap
private static ThreadLocal<ArrayMap<Animator, AnimationInfo>> sRunningAnimators =
@@ -334,6 +372,211 @@ public abstract class Transition implements Cloneable {
}
/**
+ * Sets the order in which Transition matches View start and end values.
+ * <p>
+ * The default behavior is to match first by {@link android.view.View#getViewName()},
+ * then by View instance, then by {@link android.view.View#getId()} and finally
+ * by its item ID if it is in a direct child of ListView. The caller can
+ * choose to have only some or all of the values of {@link #MATCH_INSTANCE},
+ * {@link #MATCH_VIEW_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}. Only
+ * the match algorithms supplied will be used to determine whether Views are the
+ * the same in both the start and end Scene. Views that do not match will be considered
+ * as entering or leaving the Scene.
+ * </p>
+ * @param matches A list of zero or more of {@link #MATCH_INSTANCE},
+ * {@link #MATCH_VIEW_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}.
+ * If none are provided, then the default match order will be set.
+ */
+ public void setMatchOrder(int... matches) {
+ if (matches == null || matches.length == 0) {
+ mMatchOrder = DEFAULT_MATCH_ORDER;
+ } else {
+ for (int i = 0; i < matches.length; i++) {
+ int match = matches[i];
+ if (!isValidMatch(match)) {
+ throw new IllegalArgumentException("matches contains invalid value");
+ }
+ if (alreadyContains(matches, i)) {
+ throw new IllegalArgumentException("matches contains a duplicate value");
+ }
+ }
+ mMatchOrder = matches.clone();
+ }
+ }
+
+ private static boolean isValidMatch(int match) {
+ return (match >= MATCH_FIRST && match <= MATCH_LAST);
+ }
+
+ private static boolean alreadyContains(int[] array, int searchIndex) {
+ int value = array[searchIndex];
+ for (int i = 0; i < searchIndex; i++) {
+ if (array[i] == value) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Match start/end values by View instance. Adds matched values to startValuesList
+ * and endValuesList and removes them from unmatchedStart and unmatchedEnd.
+ */
+ private void matchInstances(ArrayList<TransitionValues> startValuesList,
+ ArrayList<TransitionValues> endValuesList,
+ ArrayMap<View, TransitionValues> unmatchedStart,
+ ArrayMap<View, TransitionValues> unmatchedEnd) {
+ for (int i = unmatchedStart.size() - 1; i >= 0; i--) {
+ View view = unmatchedStart.keyAt(i);
+ TransitionValues end = unmatchedEnd.remove(view);
+ if (end != null) {
+ TransitionValues start = unmatchedStart.removeAt(i);
+ startValuesList.add(start);
+ endValuesList.add(end);
+ }
+ }
+ }
+
+ /**
+ * Match start/end values by Adapter item ID. Adds matched values to startValuesList
+ * and endValuesList and removes them from unmatchedStart and unmatchedEnd, using
+ * startItemIds and endItemIds as a guide for which Views have unique item IDs.
+ */
+ private void matchItemIds(ArrayList<TransitionValues> startValuesList,
+ ArrayList<TransitionValues> endValuesList,
+ ArrayMap<View, TransitionValues> unmatchedStart,
+ ArrayMap<View, TransitionValues> unmatchedEnd,
+ LongSparseArray<View> startItemIds, LongSparseArray<View> endItemIds) {
+ int numStartIds = startItemIds.size();
+ for (int i = 0; i < numStartIds; i++) {
+ View startView = startItemIds.valueAt(i);
+ if (startView != null) {
+ View endView = endItemIds.get(startItemIds.keyAt(i));
+ if (endView != null) {
+ TransitionValues startValues = unmatchedStart.get(startView);
+ TransitionValues endValues = unmatchedEnd.get(endView);
+ if (startValues != null && endValues != null) {
+ startValuesList.add(startValues);
+ endValuesList.add(endValues);
+ unmatchedStart.remove(startView);
+ unmatchedEnd.remove(endView);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Match start/end values by Adapter view ID. Adds matched values to startValuesList
+ * and endValuesList and removes them from unmatchedStart and unmatchedEnd, using
+ * startIds and endIds as a guide for which Views have unique IDs.
+ */
+ private void matchIds(ArrayList<TransitionValues> startValuesList,
+ ArrayList<TransitionValues> endValuesList,
+ ArrayMap<View, TransitionValues> unmatchedStart,
+ ArrayMap<View, TransitionValues> unmatchedEnd,
+ SparseArray<View> startIds, SparseArray<View> endIds) {
+ int numStartIds = startIds.size();
+ for (int i = 0; i < numStartIds; i++) {
+ View startView = startIds.valueAt(i);
+ if (startView != null && isValidTarget(startView)) {
+ View endView = endIds.get(startIds.keyAt(i));
+ if (endView != null && isValidTarget(endView)) {
+ TransitionValues startValues = unmatchedStart.get(startView);
+ TransitionValues endValues = unmatchedEnd.get(endView);
+ if (startValues != null && endValues != null) {
+ startValuesList.add(startValues);
+ endValuesList.add(endValues);
+ unmatchedStart.remove(startView);
+ unmatchedEnd.remove(endView);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Match start/end values by Adapter viewName. Adds matched values to startValuesList
+ * and endValuesList and removes them from unmatchedStart and unmatchedEnd, using
+ * startNames and endNames as a guide for which Views have unique viewNames.
+ */
+ private void matchNames(ArrayList<TransitionValues> startValuesList,
+ ArrayList<TransitionValues> endValuesList,
+ ArrayMap<View, TransitionValues> unmatchedStart,
+ ArrayMap<View, TransitionValues> unmatchedEnd,
+ ArrayMap<String, View> startNames, ArrayMap<String, View> endNames) {
+ int numStartNames = startNames.size();
+ for (int i = 0; i < numStartNames; i++) {
+ View startView = startNames.valueAt(i);
+ if (startView != null && isValidTarget(startView)) {
+ View endView = endNames.get(startNames.keyAt(i));
+ if (endView != null && isValidTarget(endView)) {
+ TransitionValues startValues = unmatchedStart.get(startView);
+ TransitionValues endValues = unmatchedEnd.get(endView);
+ if (startValues != null && endValues != null) {
+ startValuesList.add(startValues);
+ endValuesList.add(endValues);
+ unmatchedStart.remove(startView);
+ unmatchedEnd.remove(endView);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds all values from unmatchedStart and unmatchedEnd to startValuesList and endValuesList,
+ * assuming that there is no match between values in the list.
+ */
+ private void addUnmatched(ArrayList<TransitionValues> startValuesList,
+ ArrayList<TransitionValues> endValuesList,
+ ArrayMap<View, TransitionValues> unmatchedStart,
+ ArrayMap<View, TransitionValues> unmatchedEnd) {
+ // Views that only exist in the start Scene
+ for (int i = 0; i < unmatchedStart.size(); i++) {
+ startValuesList.add(unmatchedStart.valueAt(i));
+ endValuesList.add(null);
+ }
+
+ // Views that only exist in the end Scene
+ for (int i = 0; i < unmatchedEnd.size(); i++) {
+ endValuesList.add(unmatchedEnd.valueAt(i));
+ startValuesList.add(null);
+ }
+ }
+
+ private void matchStartAndEnd(TransitionValuesMaps startValues,
+ TransitionValuesMaps endValues,
+ ArrayList<TransitionValues> startValuesList,
+ ArrayList<TransitionValues> endValuesList) {
+ ArrayMap<View, TransitionValues> unmatchedStart =
+ new ArrayMap<View, TransitionValues>(startValues.viewValues);
+ ArrayMap<View, TransitionValues> unmatchedEnd =
+ new ArrayMap<View, TransitionValues>(endValues.viewValues);
+
+ for (int i = 0; i < mMatchOrder.length; i++) {
+ switch (mMatchOrder[i]) {
+ case MATCH_INSTANCE:
+ matchInstances(startValuesList, endValuesList, unmatchedStart, unmatchedEnd);
+ break;
+ case MATCH_VIEW_NAME:
+ matchNames(startValuesList, endValuesList, unmatchedStart, unmatchedEnd,
+ startValues.nameValues, endValues.nameValues);
+ break;
+ case MATCH_ID:
+ matchIds(startValuesList, endValuesList, unmatchedStart, unmatchedEnd,
+ startValues.idValues, endValues.idValues);
+ break;
+ case MATCH_ITEM_ID:
+ matchItemIds(startValuesList, endValuesList, unmatchedStart, unmatchedEnd,
+ startValues.itemIdValues, endValues.itemIdValues);
+ break;
+ }
+ }
+ addUnmatched(startValuesList, endValuesList, unmatchedStart, unmatchedEnd);
+ }
+
+ /**
* This method, essentially a wrapper around all calls to createAnimator for all
* possible target views, is called with the entire set of start/end
* values. The implementation in Transition iterates through these lists
@@ -349,76 +592,10 @@ public abstract class Transition implements Cloneable {
if (DBG) {
Log.d(LOG_TAG, "createAnimators() for " + this);
}
- ArrayMap<View, TransitionValues> endCopy =
- new ArrayMap<View, TransitionValues>(endValues.viewValues);
- SparseArray<TransitionValues> endIdCopy = endValues.idValues.clone();
- LongSparseArray<TransitionValues> endItemIdCopy = endValues.itemIdValues.clone();
- // Walk through the start values, playing everything we find
- // Remove from the end set as we go
ArrayList<TransitionValues> startValuesList = new ArrayList<TransitionValues>();
ArrayList<TransitionValues> endValuesList = new ArrayList<TransitionValues>();
- for (View view : startValues.viewValues.keySet()) {
- TransitionValues start = null;
- TransitionValues end = null;
- boolean isInListView = false;
- if (view.getParent() instanceof ListView) {
- isInListView = true;
- }
- if (!isInListView) {
- int id = view.getId();
- start = startValues.viewValues.get(view);
- end = endValues.viewValues.get(view);
- if (end != null) {
- endCopy.remove(view);
- } else if (id != View.NO_ID) {
- end = endIdCopy.get(id);
- if (end == null || startValues.viewValues.containsKey(end.view)) {
- end = null;
- id = View.NO_ID;
- } else {
- endCopy.remove(end.view);
- }
- }
- endIdCopy.remove(id);
- if (isValidTarget(view, id)) {
- startValuesList.add(start);
- endValuesList.add(end);
- }
- } else {
- ListView parent = (ListView) view.getParent();
- if (parent.getAdapter().hasStableIds()) {
- int position = parent.getPositionForView(view);
- long itemId = parent.getItemIdAtPosition(position);
- start = startValues.itemIdValues.get(itemId);
- endItemIdCopy.remove(itemId);
- // TODO: deal with targetIDs for itemIDs for ListView items
- startValuesList.add(start);
- endValuesList.add(end);
- }
- }
- }
- int startItemIdCopySize = startValues.itemIdValues.size();
- for (int i = 0; i < startItemIdCopySize; ++i) {
- long id = startValues.itemIdValues.keyAt(i);
- if (isValidTarget(null, id)) {
- TransitionValues start = startValues.itemIdValues.get(id);
- TransitionValues end = endValues.itemIdValues.get(id);
- endItemIdCopy.remove(id);
- startValuesList.add(start);
- endValuesList.add(end);
- }
- }
- // Now walk through the remains of the end set
- // We've already matched everything from start to end, everything else doesn't match.
- for (View view : endCopy.keySet()) {
- int id = view.getId();
- if (isValidTarget(view, id)) {
- TransitionValues start = null;
- TransitionValues end = endCopy.get(view);
- startValuesList.add(start);
- endValuesList.add(end);
- }
- }
+ matchStartAndEnd(startValues, endValues, startValuesList, endValuesList);
+
ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
long minStartDelay = Long.MAX_VALUE;
int minAnimator = mAnimators.size();
@@ -520,7 +697,8 @@ public abstract class Transition implements Cloneable {
* is not checked (this is in the case of ListView items, where the
* views are ignored and only the ids are used).
*/
- boolean isValidTarget(View target, long targetId) {
+ boolean isValidTarget(View target) {
+ int targetId = target.getId();
if (mTargetIdExcludes != null && mTargetIdExcludes.contains(targetId)) {
return false;
}
@@ -536,10 +714,20 @@ public abstract class Transition implements Cloneable {
}
}
}
- if (mTargetIds.size() == 0 && mTargets.size() == 0 && mTargetTypes == null) {
+ if (mTargetNameExcludes != null && target != null && target.getViewName() != null) {
+ if (mTargetNameExcludes.contains(target.getViewName())) {
+ return false;
+ }
+ }
+ if (mTargetIds.size() == 0 && mTargets.size() == 0 &&
+ (mTargetTypes == null || mTargetTypes.isEmpty() &&
+ (mTargetNames == null || mTargetNames.isEmpty()))) {
+ return true;
+ }
+ if (mTargetIds.contains(targetId) || mTargets.contains(target)) {
return true;
}
- if (mTargetIds.contains((int) targetId) || mTargets.contains(target)) {
+ if (mTargetNames != null && mTargetNames.contains(target.getViewName())) {
return true;
}
if (mTargetTypes != null) {
@@ -690,6 +878,33 @@ public abstract class Transition implements Cloneable {
}
/**
+ * Adds the viewName of a target view that this Transition is interested in
+ * animating. By default, there are no targetNames, and a Transition will
+ * listen for changes on every view in the hierarchy below the sceneRoot
+ * of the Scene being transitioned into. Setting targetNames constrains
+ * the Transition to only listen for, and act on, views with these viewNames.
+ * Views with different viewNames, or no viewName whatsoever, will be ignored.
+ *
+ * <p>Note that viewNames should be unique within the view hierarchy.</p>
+ *
+ * @see android.view.View#getViewName()
+ * @param targetName The viewName of a target view, must be non-null.
+ * @return The Transition to which the target viewName is added.
+ * Returning the same object makes it easier to chain calls during
+ * construction, such as
+ * <code>transitionSet.addTransitions(new Fade()).addTarget(someName);</code>
+ */
+ public Transition addTarget(String targetName) {
+ if (targetName != null) {
+ if (mTargetNames != null) {
+ mTargetNames = new ArrayList<String>();
+ }
+ mTargetNames.add(targetName);
+ }
+ return this;
+ }
+
+ /**
* 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
@@ -712,10 +927,12 @@ public abstract class Transition implements Cloneable {
* <code>transitionSet.addTransitions(new Fade()).addTarget(ImageView.class);</code>
*/
public Transition addTarget(Class targetType) {
- if (mTargetTypes == null) {
- mTargetTypes = new ArrayList<Class>();
+ if (targetType != null) {
+ if (mTargetTypes == null) {
+ mTargetTypes = new ArrayList<Class>();
+ }
+ mTargetTypes.add(targetType);
}
- mTargetTypes.add(targetType);
return this;
}
@@ -737,6 +954,23 @@ public abstract class Transition implements Cloneable {
}
/**
+ * Removes the given targetName from the list of viewNames that this Transition
+ * is interested in animating.
+ *
+ * @param targetName The viewName of a target view, must not be null.
+ * @return The Transition from which the targetName is removed.
+ * Returning the same object makes it easier to chain calls during
+ * construction, such as
+ * <code>transitionSet.addTransitions(new Fade()).removeTargetName(someName);</code>
+ */
+ public Transition removeTarget(String targetName) {
+ if (targetName != null && mTargetNames != null) {
+ mTargetNames.remove(targetName);
+ }
+ return this;
+ }
+
+ /**
* Whether to add the given id to the list of target ids to exclude from this
* transition. The <code>exclude</code> parameter specifies whether the target
* should be added to or removed from the excluded list.
@@ -758,7 +992,35 @@ public abstract class Transition implements Cloneable {
* @return This transition object.
*/
public Transition excludeTarget(int targetId, boolean exclude) {
- mTargetIdExcludes = excludeId(mTargetIdExcludes, targetId, exclude);
+ if (targetId >= 0) {
+ mTargetIdExcludes = excludeObject(mTargetIdExcludes, targetId, exclude);
+ }
+ return this;
+ }
+
+ /**
+ * Whether to add the given viewName to the list of target viewNames to exclude from this
+ * transition. The <code>exclude</code> parameter specifies whether the target
+ * should be added to or removed from the excluded list.
+ *
+ * <p>Excluding targets is a general mechanism for allowing transitions to run on
+ * a view hierarchy while skipping target views that should not be part of
+ * the transition. For example, you may want to avoid animating children
+ * of a specific ListView or Spinner. Views can be excluded by their
+ * id, their instance reference, their viewName, or by the Class of that view
+ * (eg, {@link Spinner}).</p>
+ *
+ * @see #excludeTarget(View, boolean)
+ * @see #excludeTarget(int, boolean)
+ * @see #excludeTarget(Class, boolean)
+ *
+ * @param targetViewName The name of a target to ignore when running this transition.
+ * @param exclude Whether to add the target to or remove the target from the
+ * current list of excluded targets.
+ * @return This transition object.
+ */
+ public Transition excludeTarget(String targetViewName, boolean exclude) {
+ mTargetNameExcludes = excludeObject(mTargetNameExcludes, targetViewName, exclude);
return this;
}
@@ -788,23 +1050,10 @@ public abstract class Transition implements Cloneable {
* @return This transition object.
*/
public Transition excludeChildren(int targetId, boolean exclude) {
- mTargetIdChildExcludes = excludeId(mTargetIdChildExcludes, targetId, exclude);
- return this;
- }
-
- /**
- * Utility method to manage the boilerplate code that is the same whether we
- * are excluding targets or their children.
- */
- private ArrayList<Integer> excludeId(ArrayList<Integer> list, int targetId, boolean exclude) {
- if (targetId > 0) {
- if (exclude) {
- list = ArrayListManager.add(list, targetId);
- } else {
- list = ArrayListManager.remove(list, targetId);
- }
+ if (targetId >= 0) {
+ mTargetIdChildExcludes = excludeObject(mTargetIdChildExcludes, targetId, exclude);
}
- return list;
+ return this;
}
/**
@@ -829,7 +1078,7 @@ public abstract class Transition implements Cloneable {
* @return This transition object.
*/
public Transition excludeTarget(View target, boolean exclude) {
- mTargetExcludes = excludeView(mTargetExcludes, target, exclude);
+ mTargetExcludes = excludeObject(mTargetExcludes, target, exclude);
return this;
}
@@ -855,7 +1104,7 @@ public abstract class Transition implements Cloneable {
* @return This transition object.
*/
public Transition excludeChildren(View target, boolean exclude) {
- mTargetChildExcludes = excludeView(mTargetChildExcludes, target, exclude);
+ mTargetChildExcludes = excludeObject(mTargetChildExcludes, target, exclude);
return this;
}
@@ -863,7 +1112,7 @@ public abstract class Transition implements Cloneable {
* Utility method to manage the boilerplate code that is the same whether we
* are excluding targets or their children.
*/
- private ArrayList<View> excludeView(ArrayList<View> list, View target, boolean exclude) {
+ private static <T> ArrayList<T> excludeObject(ArrayList<T> list, T target, boolean exclude) {
if (target != null) {
if (exclude) {
list = ArrayListManager.add(list, target);
@@ -896,7 +1145,7 @@ public abstract class Transition implements Cloneable {
* @return This transition object.
*/
public Transition excludeTarget(Class type, boolean exclude) {
- mTargetTypeExcludes = excludeType(mTargetTypeExcludes, type, exclude);
+ mTargetTypeExcludes = excludeObject(mTargetTypeExcludes, type, exclude);
return this;
}
@@ -923,26 +1172,11 @@ public abstract class Transition implements Cloneable {
* @return This transition object.
*/
public Transition excludeChildren(Class type, boolean exclude) {
- mTargetTypeChildExcludes = excludeType(mTargetTypeChildExcludes, type, exclude);
+ mTargetTypeChildExcludes = excludeObject(mTargetTypeChildExcludes, type, exclude);
return this;
}
/**
- * Utility method to manage the boilerplate code that is the same whether we
- * are excluding targets or their children.
- */
- private ArrayList<Class> excludeType(ArrayList<Class> list, Class type, boolean exclude) {
- if (type != null) {
- if (exclude) {
- list = ArrayListManager.add(list, type);
- } else {
- list = ArrayListManager.remove(list, type);
- }
- }
- return list;
- }
-
- /**
* Sets the target view instances that this Transition is interested in
* animating. By default, there are no targets, and a Transition will
* listen for changes on every view in the hierarchy below the sceneRoot
@@ -991,9 +1225,27 @@ public abstract class Transition implements Cloneable {
}
/**
- * Returns the array of target IDs that this transition limits itself to
- * tracking and animating. If the array is null for both this method and
- * {@link #getTargets()}, then this transition is
+ * Removes the given target from the list of targets that this Transition
+ * is interested in animating.
+ *
+ * @param target The type of the target view, must be non-null.
+ * @return Transition The Transition from which the target is removed.
+ * Returning the same object makes it easier to chain calls during
+ * construction, such as
+ * <code>transitionSet.addTransitions(new Fade()).removeTarget(someType);</code>
+ */
+ public Transition removeTarget(Class target) {
+ if (target != null) {
+ mTargetTypes.remove(target);
+ }
+ return this;
+ }
+
+ /**
+ * Returns the list of target IDs that this transition limits itself to
+ * tracking and animating. If the list is null or empty for
+ * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetViewNames()}, and
+ * {@link #getTargetTypes()} then this transition is
* not limited to specific views, and will handle changes to any views
* in the hierarchy of a scene change.
*
@@ -1004,9 +1256,10 @@ public abstract class Transition implements Cloneable {
}
/**
- * Returns the array of target views that this transition limits itself to
- * tracking and animating. If the array is null for both this method and
- * {@link #getTargetIds()}, then this transition is
+ * Returns the list of target views that this transition limits itself to
+ * tracking and animating. If the list is null or empty for
+ * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetViewNames()}, and
+ * {@link #getTargetTypes()} then this transition is
* not limited to specific views, and will handle changes to any views
* in the hierarchy of a scene change.
*
@@ -1017,6 +1270,34 @@ public abstract class Transition implements Cloneable {
}
/**
+ * Returns the list of target viewNames that this transition limits itself to
+ * tracking and animating. If the list is null or empty for
+ * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetViewNames()}, and
+ * {@link #getTargetTypes()} then this transition is
+ * not limited to specific views, and will handle changes to any views
+ * in the hierarchy of a scene change.
+ *
+ * @return the list of target viewNames
+ */
+ public List<String> getTargetViewNames() {
+ return mTargetNames;
+ }
+
+ /**
+ * Returns the list of target viewNames that this transition limits itself to
+ * tracking and animating. If the list is null or empty for
+ * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetViewNames()}, and
+ * {@link #getTargetTypes()} then this transition is
+ * not limited to specific views, and will handle changes to any views
+ * in the hierarchy of a scene change.
+ *
+ * @return the list of target Types
+ */
+ public List<Class> getTargetTypes() {
+ return mTargetTypes;
+ }
+
+ /**
* Recursive method that captures values for the given view and the
* hierarchy underneath it.
* @param sceneRoot The root of the view hierarchy being captured
@@ -1025,52 +1306,42 @@ public abstract class Transition implements Cloneable {
*/
void captureValues(ViewGroup sceneRoot, boolean start) {
clearValues(start);
- if (mTargetIds.size() > 0 || mTargets.size() > 0) {
- if (mTargetIds.size() > 0) {
- for (int i = 0; i < mTargetIds.size(); ++i) {
- int id = mTargetIds.get(i);
- View view = sceneRoot.findViewById(id);
- if (view != null) {
- TransitionValues values = new TransitionValues();
- values.view = view;
- if (start) {
- captureStartValues(values);
- } else {
- captureEndValues(values);
- }
- capturePropagationValues(values);
- if (start) {
- mStartValues.viewValues.put(view, values);
- if (id >= 0) {
- mStartValues.idValues.put(id, values);
- }
- } else {
- mEndValues.viewValues.put(view, values);
- if (id >= 0) {
- mEndValues.idValues.put(id, values);
- }
- }
+ if ((mTargetIds.size() > 0 || mTargets.size() > 0)
+ && (mTargetNames == null || mTargetNames.isEmpty())
+ && (mTargetTypes == null || mTargetTypes.isEmpty())) {
+ for (int i = 0; i < mTargetIds.size(); ++i) {
+ int id = mTargetIds.get(i);
+ View view = sceneRoot.findViewById(id);
+ if (view != null) {
+ TransitionValues values = new TransitionValues();
+ values.view = view;
+ if (start) {
+ captureStartValues(values);
+ } else {
+ captureEndValues(values);
+ }
+ capturePropagationValues(values);
+ if (start) {
+ addViewValues(mStartValues, view, values);
+ } else {
+ addViewValues(mEndValues, view, values);
}
}
}
- if (mTargets.size() > 0) {
- for (int i = 0; i < mTargets.size(); ++i) {
- View view = mTargets.get(i);
- if (view != null) {
- TransitionValues values = new TransitionValues();
- values.view = view;
- if (start) {
- captureStartValues(values);
- } else {
- captureEndValues(values);
- }
- capturePropagationValues(values);
- if (start) {
- mStartValues.viewValues.put(view, values);
- } else {
- mEndValues.viewValues.put(view, values);
- }
- }
+ for (int i = 0; i < mTargets.size(); ++i) {
+ View view = mTargets.get(i);
+ TransitionValues values = new TransitionValues();
+ values.view = view;
+ if (start) {
+ captureStartValues(values);
+ } else {
+ captureEndValues(values);
+ }
+ capturePropagationValues(values);
+ if (start) {
+ mStartValues.viewValues.put(view, values);
+ } else {
+ mEndValues.viewValues.put(view, values);
}
}
} else {
@@ -1078,6 +1349,47 @@ public abstract class Transition implements Cloneable {
}
}
+ static void addViewValues(TransitionValuesMaps transitionValuesMaps,
+ View view, TransitionValues transitionValues) {
+ transitionValuesMaps.viewValues.put(view, transitionValues);
+ int id = view.getId();
+ if (id >= 0) {
+ if (transitionValuesMaps.idValues.indexOfKey(id) >= 0) {
+ // Duplicate IDs cannot match by ID.
+ transitionValuesMaps.idValues.put(id, null);
+ } else {
+ transitionValuesMaps.idValues.put(id, view);
+ }
+ }
+ String name = view.getViewName();
+ if (name != null) {
+ if (transitionValuesMaps.nameValues.containsKey(name)) {
+ // Duplicate viewNames: cannot match by viewName.
+ transitionValuesMaps.nameValues.put(name, null);
+ } else {
+ transitionValuesMaps.nameValues.put(name, view);
+ }
+ }
+ if (view.getParent() instanceof ListView) {
+ ListView listview = (ListView) view.getParent();
+ if (listview.getAdapter().hasStableIds()) {
+ int position = listview.getPositionForView(view);
+ long itemId = listview.getItemIdAtPosition(position);
+ if (transitionValuesMaps.itemIdValues.indexOfKey(itemId) >= 0) {
+ // Duplicate item IDs: cannot match by item ID.
+ View alreadyMatched = transitionValuesMaps.itemIdValues.get(itemId);
+ if (alreadyMatched != null) {
+ alreadyMatched.setHasTransientState(false);
+ transitionValuesMaps.itemIdValues.put(itemId, null);
+ }
+ } else {
+ view.setHasTransientState(true);
+ transitionValuesMaps.itemIdValues.put(itemId, view);
+ }
+ }
+ }
+ }
+
/**
* Clear valuesMaps for specified start/end state
*
@@ -1109,24 +1421,7 @@ public abstract class Transition implements Cloneable {
if (view == null) {
return;
}
- boolean isListViewItem = false;
- if (view.getParent() instanceof ListView) {
- isListViewItem = true;
- }
- if (isListViewItem && !((ListView) view.getParent()).getAdapter().hasStableIds()) {
- // ignore listview children unless we can track them with stable IDs
- return;
- }
- int id = View.NO_ID;
- long itemId = View.NO_ID;
- if (!isListViewItem) {
- id = view.getId();
- } else {
- ListView listview = (ListView) view.getParent();
- int position = listview.getPositionForView(view);
- itemId = listview.getItemIdAtPosition(position);
- view.setHasTransientState(true);
- }
+ int id = view.getId();
if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) {
return;
}
@@ -1151,23 +1446,9 @@ public abstract class Transition implements Cloneable {
}
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);
- }
+ addViewValues(mStartValues, view, values);
} else {
- if (!isListViewItem) {
- mEndValues.viewValues.put(view, values);
- if (id >= 0) {
- mEndValues.idValues.put((int) id, values);
- }
- } else {
- mEndValues.itemIdValues.put(itemId, values);
- }
+ addViewValues(mEndValues, view, values);
}
}
if (view instanceof ViewGroup) {
@@ -1178,7 +1459,7 @@ public abstract class Transition implements Cloneable {
if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) {
return;
}
- if (mTargetTypeChildExcludes != null && view != null) {
+ if (mTargetTypeChildExcludes != null) {
int numTypes = mTargetTypeChildExcludes.size();
for (int i = 0; i < numTypes; ++i) {
if (mTargetTypeChildExcludes.get(i).isInstance(view)) {
@@ -1204,22 +1485,7 @@ public abstract class Transition implements Cloneable {
return mParent.getTransitionValues(view, start);
}
TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues;
- TransitionValues values = valuesMaps.viewValues.get(view);
- if (values == null) {
- int id = view.getId();
- if (id >= 0) {
- values = valuesMaps.idValues.get(id);
- }
- if (values == null && view.getParent() instanceof ListView) {
- ListView listview = (ListView) view.getParent();
- int position = listview.getPositionForView(view);
- long itemId = listview.getItemIdAtPosition(position);
- values = valuesMaps.itemIdValues.get(itemId);
- }
- // TODO: Doesn't handle the case where a view was parented to a
- // ListView (with an itemId), but no longer is
- }
- return values;
+ return valuesMaps.viewValues.get(view);
}
/**
@@ -1303,11 +1569,7 @@ public abstract class Transition implements Cloneable {
boolean cancel = false;
TransitionValues oldValues = oldInfo.values;
View oldView = oldInfo.view;
- TransitionValues newValues = mEndValues.viewValues != null ?
- mEndValues.viewValues.get(oldView) : null;
- if (newValues == null) {
- newValues = mEndValues.idValues.get(oldView.getId());
- }
+ TransitionValues newValues = mEndValues.viewValues.get(oldView);
if (oldValues != null) {
// if oldValues null, then transition didn't care to stash values,
// and won't get canceled
@@ -1429,17 +1691,15 @@ public abstract class Transition implements Cloneable {
}
}
for (int i = 0; i < mStartValues.itemIdValues.size(); ++i) {
- TransitionValues tv = mStartValues.itemIdValues.valueAt(i);
- View v = tv.view;
- if (v.hasTransientState()) {
- v.setHasTransientState(false);
+ View view = mStartValues.itemIdValues.valueAt(i);
+ if (view != null) {
+ view.setHasTransientState(false);
}
}
for (int i = 0; i < mEndValues.itemIdValues.size(); ++i) {
- TransitionValues tv = mEndValues.itemIdValues.valueAt(i);
- View v = tv.view;
- if (v.hasTransientState()) {
- v.setHasTransientState(false);
+ View view = mEndValues.itemIdValues.valueAt(i);
+ if (view != null) {
+ view.setHasTransientState(false);
}
}
mEnded = true;
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index a5e960a..f4b562f 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -30,6 +30,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.StringTokenizer;
/**
* This class inflates scenes and transitions from resource files.
@@ -40,6 +41,10 @@ import java.util.ArrayList;
* and {@link android.R.styleable#TransitionManager}.
*/
public class TransitionInflater {
+ private static final String MATCH_INSTANCE = "instance";
+ private static final String MATCH_VIEW_NAME = "viewName";
+ private static final String MATCH_ID = "id";
+ private static final String MATCH_ITEM_ID = "itemId";
private Context mContext;
@@ -229,11 +234,20 @@ public class TransitionInflater {
com.android.internal.R.styleable.TransitionTarget);
int id = a.getResourceId(
com.android.internal.R.styleable.TransitionTarget_targetId, -1);
+ String viewName;
if (id >= 0) {
transition.addTarget(id);
} else if ((id = a.getResourceId(
com.android.internal.R.styleable.TransitionTarget_excludeId, -1)) >= 0) {
transition.excludeTarget(id, true);
+ } else if ((viewName = a.getString(
+ com.android.internal.R.styleable.TransitionTarget_targetViewName))
+ != null) {
+ transition.addTarget(viewName);
+ } else if ((viewName = a.getString(
+ com.android.internal.R.styleable.TransitionTarget_excludeViewName))
+ != null) {
+ transition.excludeTarget(viewName, true);
} else {
String className = a.getString(
com.android.internal.R.styleable.TransitionTarget_excludeClass);
@@ -257,6 +271,33 @@ public class TransitionInflater {
}
}
+ private int[] parseMatchOrder(String matchOrderString) {
+ StringTokenizer st = new StringTokenizer(matchOrderString, ",");
+ int matches[] = new int[st.countTokens()];
+ int index = 0;
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken().trim();
+ if (MATCH_ID.equalsIgnoreCase(token)) {
+ matches[index] = Transition.MATCH_ID;
+ } else if (MATCH_INSTANCE.equalsIgnoreCase(token)) {
+ matches[index] = Transition.MATCH_INSTANCE;
+ } else if (MATCH_VIEW_NAME.equalsIgnoreCase(token)) {
+ matches[index] = Transition.MATCH_VIEW_NAME;
+ } else if (MATCH_ITEM_ID.equalsIgnoreCase(token)) {
+ matches[index] = Transition.MATCH_ITEM_ID;
+ } else if (token.isEmpty()) {
+ int[] smallerMatches = new int[matches.length - 1];
+ System.arraycopy(matches, 0, smallerMatches, 0, index);
+ matches = smallerMatches;
+ index--;
+ } else {
+ throw new RuntimeException("Unknown match type in matchOrder: '" + token + "'");
+ }
+ index++;
+ }
+ return matches;
+ }
+
private Transition loadTransition(Transition transition, AttributeSet attrs)
throws Resources.NotFoundException {
@@ -275,6 +316,11 @@ public class TransitionInflater {
if (resID > 0) {
transition.setInterpolator(AnimationUtils.loadInterpolator(mContext, resID));
}
+ String matchOrder =
+ a.getString(com.android.internal.R.styleable.Transition_matchOrder);
+ if (matchOrder != null) {
+ transition.setMatchOrder(parseMatchOrder(matchOrder));
+ }
a.recycle();
return transition;
}
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index 9081234..698b563 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -272,24 +272,8 @@ public class TransitionSet extends Transition {
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);
+ if (isValidTarget(view)) {
+ addViewValues(included, view, values.viewValues.valueAt(i));
}
}
return included;
@@ -328,10 +312,9 @@ public class TransitionSet extends Transition {
@Override
public void captureStartValues(TransitionValues transitionValues) {
- int targetId = transitionValues.view.getId();
- if (isValidTarget(transitionValues.view, targetId)) {
+ if (isValidTarget(transitionValues.view)) {
for (Transition childTransition : mTransitions) {
- if (childTransition.isValidTarget(transitionValues.view, targetId)) {
+ if (childTransition.isValidTarget(transitionValues.view)) {
childTransition.captureStartValues(transitionValues);
}
}
@@ -340,10 +323,9 @@ public class TransitionSet extends Transition {
@Override
public void captureEndValues(TransitionValues transitionValues) {
- int targetId = transitionValues.view.getId();
- if (isValidTarget(transitionValues.view, targetId)) {
+ if (isValidTarget(transitionValues.view)) {
for (Transition childTransition : mTransitions) {
- if (childTransition.isValidTarget(transitionValues.view, targetId)) {
+ if (childTransition.isValidTarget(transitionValues.view)) {
childTransition.captureEndValues(transitionValues);
}
}
diff --git a/core/java/android/transition/TransitionValuesMaps.java b/core/java/android/transition/TransitionValuesMaps.java
index 131596b..6d5700a 100644
--- a/core/java/android/transition/TransitionValuesMaps.java
+++ b/core/java/android/transition/TransitionValuesMaps.java
@@ -24,7 +24,7 @@ import android.view.View;
class TransitionValuesMaps {
ArrayMap<View, TransitionValues> viewValues =
new ArrayMap<View, TransitionValues>();
- SparseArray<TransitionValues> idValues = new SparseArray<TransitionValues>();
- LongSparseArray<TransitionValues> itemIdValues =
- new LongSparseArray<TransitionValues>();
+ SparseArray<View> idValues = new SparseArray<View>();
+ LongSparseArray<View> itemIdValues = new LongSparseArray<View>();
+ ArrayMap<String, View> nameValues = new ArrayMap<String, View>();
}
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index 6e6496c..0f7638b 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -162,26 +162,15 @@ public abstract class Visibility extends Transition {
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
VisibilityInfo visInfo = getVisibilityChangeInfo(startValues, endValues);
- if (visInfo.visibilityChange) {
- // Only transition views that are either targets of this transition
- // or whose parent hierarchies remain stable between scenes
- boolean isTarget = false;
- if (mTargets.size() > 0 || mTargetIds.size() > 0) {
- View startView = startValues != null ? startValues.view : null;
- View endView = endValues != null ? endValues.view : null;
- int startId = startView != null ? startView.getId() : -1;
- int endId = endView != null ? endView.getId() : -1;
- isTarget = isValidTarget(startView, startId) || isValidTarget(endView, endId);
- }
- if (isTarget || ((visInfo.startParent != null || visInfo.endParent != null))) {
- if (visInfo.fadeIn) {
- return onAppear(sceneRoot, startValues, visInfo.startVisibility,
- endValues, visInfo.endVisibility);
- } else {
- return onDisappear(sceneRoot, startValues, visInfo.startVisibility,
- endValues, visInfo.endVisibility
- );
- }
+ if (visInfo.visibilityChange
+ && (visInfo.startParent != null || visInfo.endParent != null)) {
+ if (visInfo.fadeIn) {
+ return onAppear(sceneRoot, startValues, visInfo.startVisibility,
+ endValues, visInfo.endVisibility);
+ } else {
+ return onDisappear(sceneRoot, startValues, visInfo.startVisibility,
+ endValues, visInfo.endVisibility
+ );
}
}
return null;
diff --git a/core/java/android/tv/ITvInputClient.aidl b/core/java/android/tv/ITvInputClient.aidl
deleted file mode 100644
index 538f8a1..0000000
--- a/core/java/android/tv/ITvInputClient.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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
deleted file mode 100644
index a4c99e4..0000000
--- a/core/java/android/tv/ITvInputManager.aidl
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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/ITvInputSession.aidl b/core/java/android/tv/ITvInputSession.aidl
deleted file mode 100644
index 32fee4b..0000000
--- a/core/java/android/tv/ITvInputSession.aidl
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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/ITvInputSessionWrapper.java b/core/java/android/tv/ITvInputSessionWrapper.java
deleted file mode 100644
index 3ccccf3..0000000
--- a/core/java/android/tv/ITvInputSessionWrapper.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * 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.java b/core/java/android/tv/TvInputInfo.java
deleted file mode 100644
index 90e4177..0000000
--- a/core/java/android/tv/TvInputInfo.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 7b9b1fb..0000000
--- a/core/java/android/tv/TvInputManager.java
+++ /dev/null
@@ -1,742 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 70e7f95..0000000
--- a/core/java/android/tv/TvInputService.java
+++ /dev/null
@@ -1,551 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 289823b..0000000
--- a/core/java/android/tv/TvView.java
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- * 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/Range.java b/core/java/android/util/Range.java
index 9a4bd4b..d7e8cf0 100644
--- a/core/java/android/util/Range.java
+++ b/core/java/android/util/Range.java
@@ -18,7 +18,7 @@ package android.util;
import static com.android.internal.util.Preconditions.*;
-import android.hardware.camera2.impl.HashCodeHelpers;
+import android.hardware.camera2.utils.HashCodeHelpers;
/**
* Immutable class for describing the range of two numeric values.
diff --git a/core/java/android/hardware/camera2/Rational.java b/core/java/android/util/Rational.java
index 77b8c26..8d4c67f 100644
--- a/core/java/android/hardware/camera2/Rational.java
+++ b/core/java/android/util/Rational.java
@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.hardware.camera2;
+package android.util;
/**
- * The rational data type used by CameraMetadata keys. Contains a pair of ints representing the
- * numerator and denominator of a Rational number. This type is immutable.
+ * <p>An immutable data type representation a rational number.</p>
+ *
+ * <p>Contains a pair of {@code int}s representing the numerator and denominator of a
+ * Rational number. </p>
*/
public final class Rational {
private final int mNumerator;
@@ -30,7 +32,9 @@ public final class Rational {
* is always positive.</p>
*
* <p>A rational value with a 0-denominator may be constructed, but will have similar semantics
- * as float NaN and INF values. The int getter functions return 0 in this case.</p>
+ * as float {@code NaN} and {@code INF} values. For {@code NaN},
+ * both {@link #getNumerator} and {@link #getDenominator} functions will return 0. For
+ * positive or negative {@code INF}, only the {@link #getDenominator} will return 0.</p>
*
* @param numerator the numerator of the rational
* @param denominator the denominator of the rational
@@ -91,14 +95,14 @@ public final class Rational {
* <p>A reduced form of a Rational is calculated by dividing both the numerator and the
* denominator by their greatest common divisor.</p>
*
- * <pre>
+ * <pre>{@code
* (new Rational(1, 2)).equals(new Rational(1, 2)) == true // trivially true
* (new Rational(2, 3)).equals(new Rational(1, 2)) == false // trivially false
* (new Rational(1, 2)).equals(new Rational(2, 4)) == true // true after reduction
* (new Rational(0, 0)).equals(new Rational(0, 0)) == true // NaN.equals(NaN)
* (new Rational(1, 0)).equals(new Rational(5, 0)) == true // both are +infinity
* (new Rational(1, 0)).equals(new Rational(-1, 0)) == false // +infinity != -infinity
- * </pre>
+ * }</pre>
*
* @param obj a reference to another object
*
@@ -159,16 +163,15 @@ public final class Rational {
return (float) mNumerator / (float) mDenominator;
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public int hashCode() {
- final long INT_MASK = 0xffffffffL;
-
- long asLong = INT_MASK & mNumerator;
- asLong <<= 32;
-
- asLong |= (INT_MASK & mDenominator);
+ // Bias the hash code for the first (2^16) values for both numerator and denominator
+ int numeratorFlipped = mNumerator << 16 | mNumerator >>> 16;
- return ((Long)asLong).hashCode();
+ return mDenominator ^ numeratorFlipped;
}
/**
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 33964a0..8f4b710 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -245,6 +245,9 @@ public class TimeUtils {
private static final int SECONDS_PER_HOUR = 60 * 60;
private static final int SECONDS_PER_DAY = 24 * 60 * 60;
+ /** @hide */
+ public static final long NANOS_PER_MS = 1000000;
+
private static final Object sFormatSync = new Object();
private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+5];
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 0a76075..1066430 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -112,8 +112,6 @@ public final class Choreographer {
private static final int SKIPPED_FRAME_WARNING_LIMIT = SystemProperties.getInt(
"debug.choreographer.skipwarning", 30);
- private static final long NANOS_PER_MS = 1000000;
-
private static final int MSG_DO_FRAME = 0;
private static final int MSG_DO_SCHEDULE_VSYNC = 1;
private static final int MSG_DO_SCHEDULE_CALLBACK = 2;
@@ -263,7 +261,7 @@ public final class Choreographer {
* @return The refresh rate as the nanoseconds between frames
* @hide
*/
- long getFrameIntervalNanos() {
+ public long getFrameIntervalNanos() {
return mFrameIntervalNanos;
}
@@ -456,7 +454,7 @@ public final class Choreographer {
* @hide
*/
public long getFrameTime() {
- return getFrameTimeNanos() / NANOS_PER_MS;
+ return getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
}
/**
@@ -497,7 +495,7 @@ public final class Choreographer {
}
} else {
final long nextFrameTime = Math.max(
- mLastFrameTimeNanos / NANOS_PER_MS + sFrameDelay, now);
+ mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
@@ -746,7 +744,7 @@ public final class Choreographer {
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
- mHandler.sendMessageAtTime(msg, timestampNanos / NANOS_PER_MS);
+ mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 6c451eb..5056097 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -41,10 +41,6 @@ import android.text.TextUtils;
* An implementation of Canvas on top of OpenGL ES 2.0.
*/
class GLES20Canvas extends HardwareCanvas {
- // Must match modifiers used in the JNI layer
- private static final int MODIFIER_NONE = 0;
- private static final int MODIFIER_SHADER = 2;
-
private final boolean mOpaque;
protected long mRenderer;
@@ -79,22 +75,10 @@ class GLES20Canvas extends HardwareCanvas {
// Constructors
///////////////////////////////////////////////////////////////////////////
- /**
- * Creates a canvas to render directly on screen.
- */
- GLES20Canvas(boolean translucent) {
- this(false, translucent);
- }
-
- protected GLES20Canvas(boolean record, boolean translucent) {
- mOpaque = !translucent;
-
- if (record) {
- mRenderer = nCreateDisplayListRenderer();
- } else {
- mRenderer = nCreateRenderer();
- }
-
+ // TODO: Merge with GLES20RecordingCanvas
+ protected GLES20Canvas() {
+ mOpaque = false;
+ mRenderer = nCreateDisplayListRenderer();
setupFinalizer();
}
@@ -106,7 +90,6 @@ class GLES20Canvas extends HardwareCanvas {
}
}
- private static native long nCreateRenderer();
private static native long nCreateDisplayListRenderer();
private static native void nResetDisplayListRenderer(long renderer);
private static native void nDestroyRenderer(long renderer);
@@ -135,36 +118,6 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nSetProperty(String name, String value);
///////////////////////////////////////////////////////////////////////////
- // Hardware layers
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- void pushLayerUpdate(HardwareLayer layer) {
- nPushLayerUpdate(mRenderer, layer.getLayer());
- }
-
- @Override
- void cancelLayerUpdate(HardwareLayer layer) {
- nCancelLayerUpdate(mRenderer, layer.getLayer());
- }
-
- @Override
- void flushLayerUpdates() {
- nFlushLayerUpdates(mRenderer);
- }
-
- @Override
- void clearLayerUpdates() {
- nClearLayerUpdates(mRenderer);
- }
-
- 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);
- private static native void nCancelLayerUpdate(long renderer, long layer);
-
- ///////////////////////////////////////////////////////////////////////////
// Canvas management
///////////////////////////////////////////////////////////////////////////
@@ -238,20 +191,6 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nFinish(long renderer);
- /**
- * Returns the size of the stencil buffer required by the underlying
- * implementation.
- *
- * @return The minimum number of bits the stencil buffer must. Always >= 0.
- *
- * @hide
- */
- public static int getStencilSize() {
- return nGetStencilSize();
- }
-
- private static native int nGetStencilSize();
-
///////////////////////////////////////////////////////////////////////////
// Functor
///////////////////////////////////////////////////////////////////////////
@@ -288,49 +227,6 @@ class GLES20Canvas extends HardwareCanvas {
*/
static final int FLUSH_CACHES_FULL = 2;
- /**
- * Flush caches to reclaim as much memory as possible. The amount of memory
- * to reclaim is indicate by the level parameter.
- *
- * The level can be one of {@link #FLUSH_CACHES_MODERATE} or
- * {@link #FLUSH_CACHES_FULL}.
- *
- * @param level Hint about the amount of memory to reclaim
- */
- static void flushCaches(int level) {
- nFlushCaches(level);
- }
-
- private static native void nFlushCaches(int level);
-
- /**
- * Release all resources associated with the underlying caches. This should
- * only be called after a full flushCaches().
- *
- * @hide
- */
- static void terminateCaches() {
- nTerminateCaches();
- }
-
- private static native void nTerminateCaches();
-
- static boolean initCaches() {
- return nInitCaches();
- }
-
- private static native boolean nInitCaches();
-
- ///////////////////////////////////////////////////////////////////////////
- // Atlas
- ///////////////////////////////////////////////////////////////////////////
-
- static void initAtlas(GraphicBuffer buffer, long[] map) {
- nInitAtlas(buffer, map, map.length);
- }
-
- private static native void nInitAtlas(GraphicBuffer buffer, long[] map, int count);
-
///////////////////////////////////////////////////////////////////////////
// Display list
///////////////////////////////////////////////////////////////////////////
@@ -650,13 +546,8 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,
Paint paint) {
- int modifiers = setupModifiers(paint, MODIFIER_SHADER);
- try {
- nDrawArc(mRenderer, oval.left, oval.top, oval.right, oval.bottom,
- startAngle, sweepAngle, useCenter, paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ nDrawArc(mRenderer, oval.left, oval.top, oval.right, oval.bottom,
+ startAngle, sweepAngle, useCenter, paint.mNativePaint);
}
private static native void nDrawArc(long renderer, float left, float top,
@@ -672,7 +563,6 @@ class GLES20Canvas extends HardwareCanvas {
public void drawPatch(NinePatch patch, Rect dst, Paint paint) {
Bitmap bitmap = patch.getBitmap();
throwIfCannotDraw(bitmap);
- // Shaders are ignored when drawing patches
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);
@@ -682,7 +572,6 @@ class GLES20Canvas extends HardwareCanvas {
public void drawPatch(NinePatch patch, RectF dst, Paint paint) {
Bitmap bitmap = patch.getBitmap();
throwIfCannotDraw(bitmap);
- // Shaders are ignored when drawing patches
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);
@@ -694,14 +583,8 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
throwIfCannotDraw(bitmap);
- // Shaders are ignored when drawing bitmaps
- int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
- try {
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ final long nativePaint = paint == null ? 0 : paint.mNativePaint;
+ nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint);
}
private static native void nDrawBitmap(long renderer, long bitmap, byte[] buffer,
@@ -710,15 +593,9 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
throwIfCannotDraw(bitmap);
- // Shaders are ignored when drawing bitmaps
- int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
- try {
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer,
- matrix.native_instance, nativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ final long nativePaint = paint == null ? 0 : paint.mNativePaint;
+ nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer,
+ matrix.native_instance, nativePaint);
}
private static native void nDrawBitmap(long renderer, long bitmap, byte[] buffer,
@@ -727,55 +604,43 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
throwIfCannotDraw(bitmap);
- // Shaders are ignored when drawing bitmaps
- int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
- try {
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
-
- int left, top, right, bottom;
- if (src == null) {
- left = top = 0;
- right = bitmap.getWidth();
- bottom = bitmap.getHeight();
- } else {
- left = src.left;
- right = src.right;
- top = src.top;
- bottom = src.bottom;
- }
+ final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ int left, top, right, bottom;
+ if (src == null) {
+ left = top = 0;
+ right = bitmap.getWidth();
+ bottom = bitmap.getHeight();
+ } else {
+ left = src.left;
+ right = src.right;
+ top = src.top;
+ bottom = src.bottom;
}
+
+ nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint);
}
@Override
public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) {
throwIfCannotDraw(bitmap);
- // Shaders are ignored when drawing bitmaps
- int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
- try {
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
-
- float left, top, right, bottom;
- if (src == null) {
- left = top = 0;
- right = bitmap.getWidth();
- bottom = bitmap.getHeight();
- } else {
- left = src.left;
- right = src.right;
- top = src.top;
- bottom = src.bottom;
- }
-
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ final long nativePaint = paint == null ? 0 : paint.mNativePaint;
+
+ float left, top, right, bottom;
+ if (src == null) {
+ left = top = 0;
+ right = bitmap.getWidth();
+ bottom = bitmap.getHeight();
+ } else {
+ left = src.left;
+ right = src.right;
+ top = src.top;
+ bottom = src.bottom;
}
+
+ nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint);
}
private static native void nDrawBitmap(long renderer, long bitmap, byte[] buffer,
@@ -805,7 +670,6 @@ class GLES20Canvas extends HardwareCanvas {
throw new ArrayIndexOutOfBoundsException();
}
- // Shaders are ignored when drawing bitmaps
final long nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawBitmap(mRenderer, colors, offset, stride, x, y,
width, height, hasAlpha, nativePaint);
@@ -817,7 +681,6 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawBitmap(int[] colors, int offset, int stride, int x, int y,
int width, int height, boolean hasAlpha, Paint paint) {
- // Shaders are ignored when drawing bitmaps
drawBitmap(colors, offset, stride, (float) x, (float) y, width, height, hasAlpha, paint);
}
@@ -840,14 +703,9 @@ class GLES20Canvas extends HardwareCanvas {
checkRange(colors.length, colorOffset, count);
}
- int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
- try {
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, meshWidth, meshHeight,
- verts, vertOffset, colors, colorOffset, nativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ final long nativePaint = paint == null ? 0 : paint.mNativePaint;
+ nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, meshWidth, meshHeight,
+ verts, vertOffset, colors, colorOffset, nativePaint);
}
private static native void nDrawBitmapMesh(long renderer, long bitmap, byte[] buffer,
@@ -856,12 +714,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawCircle(float cx, float cy, float radius, Paint paint) {
- int modifiers = setupModifiers(paint, MODIFIER_SHADER);
- try {
- nDrawCircle(mRenderer, cx, cy, radius, paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ nDrawCircle(mRenderer, cx, cy, radius, paint.mNativePaint);
}
private static native void nDrawCircle(long renderer, float cx, float cy,
@@ -906,12 +759,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_SHADER);
- try {
- nDrawLines(mRenderer, pts, offset, count, paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ nDrawLines(mRenderer, pts, offset, count, paint.mNativePaint);
}
private static native void nDrawLines(long renderer, float[] points,
@@ -924,12 +772,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawOval(RectF oval, Paint paint) {
- int modifiers = setupModifiers(paint, MODIFIER_SHADER);
- try {
- nDrawOval(mRenderer, oval.left, oval.top, oval.right, oval.bottom, paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ nDrawOval(mRenderer, oval.left, oval.top, oval.right, oval.bottom, paint.mNativePaint);
}
private static native void nDrawOval(long renderer, float left, float top,
@@ -944,34 +787,18 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawPath(Path path, Paint paint) {
- int modifiers = setupModifiers(paint, MODIFIER_SHADER);
- try {
- if (path.isSimplePath) {
- if (path.rects != null) {
- nDrawRects(mRenderer, path.rects.mNativeRegion, paint.mNativePaint);
- }
- } else {
- nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint);
+ if (path.isSimplePath) {
+ if (path.rects != null) {
+ nDrawRects(mRenderer, path.rects.mNativeRegion, paint.mNativePaint);
}
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ } else {
+ nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint);
}
}
private static native void nDrawPath(long renderer, long path, long paint);
private static native void nDrawRects(long renderer, long region, long paint);
- void drawRects(float[] rects, int count, Paint paint) {
- int modifiers = setupModifiers(paint, MODIFIER_SHADER);
- try {
- nDrawRects(mRenderer, rects, count, paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
- }
-
- private static native void nDrawRects(long renderer, float[] rects, int count, long paint);
-
@Override
public void drawPicture(Picture picture) {
if (picture.createdFromStream) {
@@ -1029,12 +856,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_SHADER);
- try {
- nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint);
}
private static native void nDrawPoints(long renderer, float[] points,
@@ -1047,12 +869,7 @@ class GLES20Canvas extends HardwareCanvas {
throw new IndexOutOfBoundsException();
}
- int modifiers = setupModifiers(paint);
- try {
- nDrawPosText(mRenderer, text, index, count, pos, paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ nDrawPosText(mRenderer, text, index, count, pos, paint.mNativePaint);
}
private static native void nDrawPosText(long renderer, char[] text, int index, int count,
@@ -1065,12 +882,7 @@ class GLES20Canvas extends HardwareCanvas {
throw new ArrayIndexOutOfBoundsException();
}
- int modifiers = setupModifiers(paint);
- try {
- nDrawPosText(mRenderer, text, 0, text.length(), pos, paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ nDrawPosText(mRenderer, text, 0, text.length(), pos, paint.mNativePaint);
}
private static native void nDrawPosText(long renderer, String text, int start, int end,
@@ -1079,12 +891,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_SHADER);
- try {
- nDrawRect(mRenderer, left, top, right, bottom, paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ nDrawRect(mRenderer, left, top, right, bottom, paint.mNativePaint);
}
private static native void nDrawRect(long renderer, float left, float top,
@@ -1108,12 +915,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
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, left, top, right, bottom, rx, ry, paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ nDrawRoundRect(mRenderer, left, top, right, bottom, rx, ry, paint.mNativePaint);
}
private static native void nDrawRoundRect(long renderer, float left, float top,
@@ -1125,37 +927,27 @@ class GLES20Canvas extends HardwareCanvas {
throw new IndexOutOfBoundsException();
}
- int modifiers = setupModifiers(paint);
- try {
- nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ nDrawText(mRenderer, text, index, count, x, y,
+ paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
}
private static native void nDrawText(long renderer, char[] text, int index, int count,
- float x, float y, int bidiFlags, long paint);
+ float x, float y, int bidiFlags, long paint, long typeface);
@Override
public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
- int modifiers = setupModifiers(paint);
- try {
- if (text instanceof String || text instanceof SpannedString ||
- text instanceof SpannableString) {
- nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags,
- paint.mNativePaint);
- } else if (text instanceof GraphicsOperations) {
- ((GraphicsOperations) text).drawText(this, start, end, x, y,
- paint);
- } else {
- char[] buf = TemporaryBuffer.obtain(end - start);
- TextUtils.getChars(text, start, end, buf, 0);
- nDrawText(mRenderer, buf, 0, end - start, x, y,
- paint.mBidiFlags, paint.mNativePaint);
- TemporaryBuffer.recycle(buf);
- }
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ if (text instanceof String || text instanceof SpannedString ||
+ text instanceof SpannableString) {
+ nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags,
+ paint.mNativePaint, paint.mNativeTypeface);
+ } else if (text instanceof GraphicsOperations) {
+ ((GraphicsOperations) text).drawText(this, start, end, x, y, paint);
+ } else {
+ char[] buf = TemporaryBuffer.obtain(end - start);
+ TextUtils.getChars(text, start, end, buf, 0);
+ nDrawText(mRenderer, buf, 0, end - start, x, y,
+ paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
+ TemporaryBuffer.recycle(buf);
}
}
@@ -1165,26 +957,17 @@ class GLES20Canvas extends HardwareCanvas {
throw new IndexOutOfBoundsException();
}
- int modifiers = setupModifiers(paint);
- try {
- nDrawText(mRenderer, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ nDrawText(mRenderer, text, start, end, x, y,
+ paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
}
private static native void nDrawText(long renderer, String text, int start, int end,
- float x, float y, int bidiFlags, long paint);
+ float x, float y, int bidiFlags, long paint, long typeface);
@Override
public void drawText(String text, float x, float y, Paint paint) {
- int modifiers = setupModifiers(paint);
- try {
- nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mBidiFlags,
- paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ nDrawText(mRenderer, text, 0, text.length(), x, y,
+ paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
}
@Override
@@ -1194,13 +977,8 @@ class GLES20Canvas extends HardwareCanvas {
throw new ArrayIndexOutOfBoundsException();
}
- int modifiers = setupModifiers(paint);
- try {
- nDrawTextOnPath(mRenderer, text, index, count, path.mNativePath, hOffset, vOffset,
- paint.mBidiFlags, paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ nDrawTextOnPath(mRenderer, text, index, count, path.mNativePath, hOffset, vOffset,
+ paint.mBidiFlags, paint.mNativePaint);
}
private static native void nDrawTextOnPath(long renderer, char[] text, int index, int count,
@@ -1210,13 +988,8 @@ class GLES20Canvas extends HardwareCanvas {
public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) {
if (text.length() == 0) return;
- int modifiers = setupModifiers(paint);
- try {
- nDrawTextOnPath(mRenderer, text, 0, text.length(), path.mNativePath, hOffset, vOffset,
- paint.mBidiFlags, paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ nDrawTextOnPath(mRenderer, text, 0, text.length(), path.mNativePath, hOffset, vOffset,
+ paint.mBidiFlags, paint.mNativePaint);
}
private static native void nDrawTextOnPath(long renderer, String text, int start, int end,
@@ -1232,17 +1005,12 @@ class GLES20Canvas extends HardwareCanvas {
throw new IllegalArgumentException("Unknown direction: " + dir);
}
- int modifiers = setupModifiers(paint);
- try {
- nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, dir,
- paint.mNativePaint);
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
- }
+ nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, dir,
+ paint.mNativePaint, paint.mNativeTypeface);
}
private static native void nDrawTextRun(long renderer, char[] text, int index, int count,
- int contextIndex, int contextCount, float x, float y, int dir, long nativePaint);
+ int contextIndex, int contextCount, float x, float y, int dir, long nativePaint, long nativeTypeface);
@Override
public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd,
@@ -1251,32 +1019,27 @@ class GLES20Canvas extends HardwareCanvas {
throw new IndexOutOfBoundsException();
}
- int modifiers = setupModifiers(paint);
- try {
- int flags = dir == 0 ? 0 : 1;
- if (text instanceof String || text instanceof SpannedString ||
- text instanceof SpannableString) {
- nDrawTextRun(mRenderer, text.toString(), start, end, contextStart,
- contextEnd, x, y, flags, paint.mNativePaint);
- } else if (text instanceof GraphicsOperations) {
- ((GraphicsOperations) text).drawTextRun(this, start, end,
- contextStart, contextEnd, x, y, flags, paint);
- } else {
- int contextLen = contextEnd - contextStart;
- int len = end - start;
- char[] buf = TemporaryBuffer.obtain(contextLen);
- TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
- nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen,
- x, y, flags, paint.mNativePaint);
- TemporaryBuffer.recycle(buf);
- }
- } finally {
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ int flags = dir == 0 ? 0 : 1;
+ if (text instanceof String || text instanceof SpannedString ||
+ text instanceof SpannableString) {
+ nDrawTextRun(mRenderer, text.toString(), start, end, contextStart,
+ contextEnd, x, y, flags, paint.mNativePaint, paint.mNativeTypeface);
+ } else if (text instanceof GraphicsOperations) {
+ ((GraphicsOperations) text).drawTextRun(this, start, end,
+ contextStart, contextEnd, x, y, flags, paint);
+ } else {
+ int contextLen = contextEnd - contextStart;
+ int len = end - start;
+ char[] buf = TemporaryBuffer.obtain(contextLen);
+ TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+ nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen,
+ x, y, flags, paint.mNativePaint, paint.mNativeTypeface);
+ TemporaryBuffer.recycle(buf);
}
}
private static native void nDrawTextRun(long renderer, String text, int start, int end,
- int contextStart, int contextEnd, float x, float y, int flags, long nativePaint);
+ int contextStart, int contextEnd, float x, float y, int flags, long nativePaint, long nativeTypeface);
@Override
public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset,
@@ -1284,40 +1047,4 @@ class GLES20Canvas extends HardwareCanvas {
int indexOffset, int indexCount, Paint paint) {
// TODO: Implement
}
-
- private int setupModifiers(Bitmap b, Paint paint) {
- if (b.getConfig() != Bitmap.Config.ALPHA_8) {
- return MODIFIER_NONE;
- } else {
- return setupModifiers(paint);
- }
- }
-
- private int setupModifiers(Paint paint) {
- int modifiers = MODIFIER_NONE;
-
- final Shader shader = paint.getShader();
- if (shader != null) {
- nSetupShader(mRenderer, shader.native_shader);
- modifiers |= MODIFIER_SHADER;
- }
-
- return modifiers;
- }
-
- private int setupModifiers(Paint paint, int flags) {
- int modifiers = MODIFIER_NONE;
-
- final Shader shader = paint.getShader();
- if (shader != null && (flags & MODIFIER_SHADER) != 0) {
- nSetupShader(mRenderer, shader.native_shader);
- modifiers |= MODIFIER_SHADER;
- }
-
- return modifiers;
- }
-
- private static native void nSetupShader(long renderer, long shader);
-
- private static native void nResetModifiers(long renderer, int modifiers);
}
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
index a94ec3a..b2961e5 100644
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ b/core/java/android/view/GLES20RecordingCanvas.java
@@ -36,7 +36,7 @@ class GLES20RecordingCanvas extends GLES20Canvas {
RenderNode mNode;
private GLES20RecordingCanvas() {
- super(true, true);
+ super();
}
static GLES20RecordingCanvas obtain(@NonNull RenderNode node) {
diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java
deleted file mode 100644
index 7b49006..0000000
--- a/core/java/android/view/GLRenderer.java
+++ /dev/null
@@ -1,1537 +0,0 @@
-/*
- * 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 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
- void setOpaque(boolean opaque) {
- // Not supported
- }
-
- @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;
- }
-
- @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);
- }
- }
- }
-
- /**
- * 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/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index 9568760..b8e7d8c 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -110,48 +110,6 @@ public abstract class HardwareCanvas extends Canvas {
return RenderNode.STATUS_DONE;
}
- /**
- * Indicates that the specified layer must be updated as soon as possible.
- *
- * @param layer The layer to update
- *
- * @see #clearLayerUpdates()
- *
- * @hide
- */
- 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)
- * @see #clearLayerUpdates()
- *
- * @hide
- */
- abstract void cancelLayerUpdate(HardwareLayer layer);
-
- /**
- * Immediately executes all enqueued layer updates.
- *
- * @see #pushLayerUpdate(HardwareLayer)
- *
- * @hide
- */
- abstract void flushLayerUpdates();
-
- /**
- * Removes all enqueued layer updates.
- *
- * @see #pushLayerUpdate(HardwareLayer)
- *
- * @hide
- */
- abstract void clearLayerUpdates();
-
public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
CanvasProperty<Float> radius, CanvasProperty<Paint> paint);
}
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index 4d78733..6acb134 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -22,6 +22,8 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
+import com.android.internal.util.VirtualRefBasePtr;
+
/**
* A hardware layer can be used to render graphics operations into a hardware
* friendly buffer. For instance, with an OpenGL backend a hardware layer
@@ -36,7 +38,7 @@ final class HardwareLayer {
private static final int LAYER_TYPE_DISPLAY_LIST = 2;
private HardwareRenderer mRenderer;
- private Finalizer mFinalizer;
+ private VirtualRefBasePtr mFinalizer;
private RenderNode mDisplayList;
private final int mLayerType;
@@ -47,10 +49,7 @@ final class HardwareLayer {
}
mRenderer = renderer;
mLayerType = type;
- mFinalizer = new Finalizer(deferredUpdater);
-
- // Layer is considered initialized at this point, notify the HardwareRenderer
- mRenderer.onLayerCreated(this);
+ mFinalizer = new VirtualRefBasePtr(deferredUpdater);
}
private void assertType(int type) {
@@ -59,6 +58,10 @@ final class HardwareLayer {
}
}
+ boolean hasDisplayList() {
+ return mDisplayList != null;
+ }
+
/**
* Update the paint used when drawing this layer.
*
@@ -66,7 +69,8 @@ final class HardwareLayer {
* @see View#setLayerPaint(android.graphics.Paint)
*/
public void setLayerPaint(Paint paint) {
- nSetLayerPaint(mFinalizer.mDeferredUpdater, paint.mNativePaint);
+ nSetLayerPaint(mFinalizer.get(), paint.mNativePaint);
+ mRenderer.pushLayerUpdate(this);
}
/**
@@ -75,7 +79,7 @@ final class HardwareLayer {
* @return True if the layer can be rendered into, false otherwise
*/
public boolean isValid() {
- return mFinalizer != null && mFinalizer.mDeferredUpdater != 0;
+ return mFinalizer != null && mFinalizer.get() != 0;
}
/**
@@ -91,35 +95,14 @@ final class HardwareLayer {
mDisplayList.destroyDisplayListData();
mDisplayList = null;
}
- if (mRenderer != null) {
- mRenderer.onLayerDestroyed(this);
- mRenderer = null;
- }
- doDestroyLayerUpdater();
+ mRenderer.onLayerDestroyed(this);
+ mRenderer = null;
+ mFinalizer.release();
+ mFinalizer = null;
}
public long getDeferredLayerUpdater() {
- return mFinalizer.mDeferredUpdater;
- }
-
- /**
- * 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.
- *
- * It is safe to call this in onLayerDestroyed only
- */
- public long detachBackingLayer() {
- long backingLayer = nDetachBackingLayer(mFinalizer.mDeferredUpdater);
- doDestroyLayerUpdater();
- return backingLayer;
- }
-
- private void doDestroyLayerUpdater() {
- if (mFinalizer != null) {
- mFinalizer.destroy();
- mFinalizer = null;
- }
+ return mFinalizer.get();
}
public RenderNode startRecording() {
@@ -132,7 +115,7 @@ final class HardwareLayer {
}
public void endRecording(Rect dirtyRect) {
- nUpdateRenderLayer(mFinalizer.mDeferredUpdater, mDisplayList.getNativeDisplayList(),
+ nUpdateRenderLayer(mFinalizer.get(), mDisplayList.getNativeDisplayList(),
dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom);
mRenderer.pushLayerUpdate(this);
}
@@ -160,7 +143,7 @@ final class HardwareLayer {
* match the desired values.
*/
public boolean prepare(int width, int height, boolean isOpaque) {
- return nPrepare(mFinalizer.mDeferredUpdater, width, height, isOpaque);
+ return nPrepare(mFinalizer.get(), width, height, isOpaque);
}
/**
@@ -169,7 +152,8 @@ final class HardwareLayer {
* @param matrix The transform to apply to the layer.
*/
public void setTransform(Matrix matrix) {
- nSetTransform(mFinalizer.mDeferredUpdater, matrix.native_instance);
+ nSetTransform(mFinalizer.get(), matrix.native_instance);
+ mRenderer.pushLayerUpdate(this);
}
/**
@@ -183,41 +167,25 @@ final class HardwareLayer {
surface.detachFromGLContext();
// SurfaceTexture owns the texture name and detachFromGLContext
// should have deleted it
- nOnTextureDestroyed(mFinalizer.mDeferredUpdater);
+ nOnTextureDestroyed(mFinalizer.get());
}
});
}
- /**
- * 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
- */
- @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);
+ return nGetLayer(mFinalizer.get());
}
public void setSurfaceTexture(SurfaceTexture surface) {
assertType(LAYER_TYPE_TEXTURE);
- nSetSurfaceTexture(mFinalizer.mDeferredUpdater, surface, false);
+ nSetSurfaceTexture(mFinalizer.get(), surface, false);
+ mRenderer.pushLayerUpdate(this);
}
public void updateSurfaceTexture() {
assertType(LAYER_TYPE_TEXTURE);
- nUpdateSurfaceTexture(mFinalizer.mDeferredUpdater);
+ nUpdateSurfaceTexture(mFinalizer.get());
+ mRenderer.pushLayerUpdate(this);
}
/**
@@ -225,48 +193,20 @@ final class HardwareLayer {
*/
SurfaceTexture createSurfaceTexture() {
assertType(LAYER_TYPE_TEXTURE);
- SurfaceTexture st = new SurfaceTexture(nGetTexName(mFinalizer.mDeferredUpdater));
- nSetSurfaceTexture(mFinalizer.mDeferredUpdater, st, true);
+ SurfaceTexture st = new SurfaceTexture(nGetTexName(mFinalizer.get()));
+ nSetSurfaceTexture(mFinalizer.get(), st, true);
return st;
}
- /**
- * This should only be used by HardwareRenderer! Do not call directly
- */
- 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);
}
- /**
- * This should only be used by HardwareRenderer! Do not call directly
- */
- static HardwareLayer createDisplayListLayer(HardwareRenderer renderer,
- int width, int height) {
- return new HardwareLayer(renderer, nCreateRenderLayer(width, height), LAYER_TYPE_DISPLAY_LIST);
- }
-
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.
- */
- 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);
@@ -281,28 +221,4 @@ final class HardwareLayer {
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 e366697..d67c974 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -17,13 +17,13 @@
package android.view;
import android.graphics.Bitmap;
-import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.util.DisplayMetrics;
import android.view.Surface.OutOfResourcesException;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
@@ -61,11 +61,9 @@ public abstract class HardwareRenderer {
* Possible values:
* "true", to enable profiling
* "visual_bars", to enable profiling and visualize the results on screen
- * "visual_lines", to enable profiling and visualize the results on screen
* "false", to disable profiling
*
* @see #PROFILE_PROPERTY_VISUALIZE_BARS
- * @see #PROFILE_PROPERTY_VISUALIZE_LINES
*
* @hide
*/
@@ -80,14 +78,6 @@ public abstract class HardwareRenderer {
public static final String PROFILE_PROPERTY_VISUALIZE_BARS = "visual_bars";
/**
- * Value for {@link #PROFILE_PROPERTY}. When the property is set to this
- * value, profiling data will be visualized on screen as a line chart.
- *
- * @hide
- */
- public static final String PROFILE_PROPERTY_VISUALIZE_LINES = "visual_lines";
-
- /**
* System property used to specify the number of frames to be used
* when doing hardware rendering profiling.
* The default value of this property is #PROFILE_MAX_FRAMES.
@@ -181,9 +171,6 @@ public abstract class HardwareRenderer {
*/
public static boolean sSystemRendererDisabled = false;
- /** @hide */
- public static boolean sUseRenderThread = true;
-
private boolean mEnabled;
private boolean mRequested = true;
@@ -273,12 +260,16 @@ public abstract class HardwareRenderer {
*
* @param width Width of the drawing surface.
* @param height Height of the drawing surface.
+ * @param lightX X position of the shadow casting light
+ * @param lightY Y position of the shadow casting light
+ * @param lightZ Z position of the shadow casting light
+ * @param lightRadius radius of the shadow casting light
*/
- abstract void setup(int width, int height);
+ abstract void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius);
/**
* Gets the current width of the surface. This is the width that the surface
- * was last set to in a call to {@link #setup(int, int)}.
+ * was last set to in a call to {@link #setup(int, int, float, float, float, float)}.
*
* @return the current width of the surface
*/
@@ -286,7 +277,7 @@ public abstract class HardwareRenderer {
/**
* Gets the current height of the surface. This is the height that the surface
- * was last set to in a call to {@link #setup(int, int)}.
+ * was last set to in a call to {@link #setup(int, int, float, float, float, float)}.
*
* @return the current width of the surface
*/
@@ -294,25 +285,14 @@ public abstract class HardwareRenderer {
/**
* Outputs extra debugging information in the specified file descriptor.
- * @param pw
- */
- abstract void dumpGfxInfo(PrintWriter pw);
-
- /**
- * Outputs the total number of frames rendered (used for fps calculations)
- *
- * @return the number of frames rendered
*/
- abstract long getFrameCount();
+ abstract void dumpGfxInfo(PrintWriter pw, FileDescriptor fd);
/**
* Loads system properties used by the renderer. This method is invoked
* whenever system properties are modified. Implementations can use this
* to trigger live updates of the renderer based on properties.
*
- * @param surface The surface to update with the new properties.
- * Can be null.
- *
* @return True if a property has changed.
*/
abstract boolean loadSystemProperties();
@@ -326,7 +306,7 @@ public abstract class HardwareRenderer {
* @hide
*/
public static void setupDiskCache(File cacheDir) {
- GLRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
+ ThreadedRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
}
/**
@@ -340,12 +320,6 @@ public abstract class HardwareRenderer {
abstract void pushLayerUpdate(HardwareLayer layer);
/**
- * 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 onLayerCreated(HardwareLayer hardwareLayer);
-
- /**
* Tells the HardwareRenderer that the layer is destroyed. The renderer
* should remove the layer from any update queues.
*/
@@ -443,17 +417,18 @@ public abstract class HardwareRenderer {
* @param width The width of the drawing surface.
* @param height The height of the drawing surface.
* @param surface The surface to hardware accelerate
+ * @param metrics The display metrics used to draw the output.
*
* @return true if the surface was initialized, false otherwise. Returning
* false might mean that the surface was already initialized.
*/
- boolean initializeIfNeeded(int width, int height, Surface surface)
+ boolean initializeIfNeeded(int width, int height, Surface surface, DisplayMetrics metrics)
throws OutOfResourcesException {
if (isRequested()) {
// We lost the gl context, so recreate it.
if (!isEnabled()) {
if (initialize(surface)) {
- setup(width, height);
+ setup(width, height, metrics);
return true;
}
}
@@ -461,6 +436,14 @@ public abstract class HardwareRenderer {
return false;
}
+ void setup(int width, int height, DisplayMetrics metrics) {
+ float lightX = width / 2.0f;
+ float lightY = -400 * metrics.density;
+ float lightZ = 800 * metrics.density;
+ float lightRadius = 800 * metrics.density;
+ setup(width, height, lightX, lightY, lightZ, lightRadius);
+ }
+
/**
* Optional, sets the name of the renderer. Useful for debugging purposes.
*
@@ -483,11 +466,7 @@ public abstract class HardwareRenderer {
static HardwareRenderer create(boolean translucent) {
HardwareRenderer renderer = null;
if (GLES20Canvas.isAvailable()) {
- if (sUseRenderThread) {
- renderer = new ThreadedRenderer(translucent);
- } else {
- renderer = new GLRenderer(translucent);
- }
+ renderer = new ThreadedRenderer(translucent);
}
return renderer;
}
@@ -514,7 +493,7 @@ public abstract class HardwareRenderer {
* see {@link android.content.ComponentCallbacks}
*/
static void startTrimMemory(int level) {
- GLRenderer.startTrimMemory(level);
+ ThreadedRenderer.startTrimMemory(level);
}
/**
@@ -522,7 +501,7 @@ public abstract class HardwareRenderer {
* cleanup special resources used by the memory trimming process.
*/
static void endTrimMemory() {
- GLRenderer.endTrimMemory();
+ ThreadedRenderer.endTrimMemory();
}
/**
@@ -569,96 +548,8 @@ public abstract class HardwareRenderer {
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.
+ * Called by {@link ViewRootImpl} when a new performTraverals is scheduled.
*/
- abstract class GraphDataProvider {
- /**
- * Draws the graph as bars. Frame elements are stacked on top of
- * each other.
- */
- public static final int GRAPH_TYPE_BARS = 0;
- /**
- * Draws the graph as lines. The number of series drawn corresponds
- * to the number of elements.
- */
- public static final int GRAPH_TYPE_LINES = 1;
-
- /**
- * Returns the type of graph to render.
- *
- * @return {@link #GRAPH_TYPE_BARS} or {@link #GRAPH_TYPE_LINES}
- */
- abstract int getGraphType();
-
- /**
- * This method is invoked before the graph is drawn. This method
- * can be used to compute sizes, etc.
- *
- * @param metrics The display metrics
- */
- abstract void prepare(DisplayMetrics metrics);
-
- /**
- * @return The size in pixels of a vertical unit.
- */
- abstract int getVerticalUnitSize();
-
- /**
- * @return The size in pixels of a horizontal unit.
- */
- abstract int getHorizontalUnitSize();
-
- /**
- * @return The size in pixels of the margin between horizontal units.
- */
- abstract int getHorizontaUnitMargin();
-
- /**
- * An optional threshold value.
- *
- * @return A value >= 0 to draw the threshold, a negative value
- * to ignore it.
- */
- abstract float getThreshold();
-
- /**
- * The data to draw in the graph. The number of elements in the
- * array must be at least {@link #getFrameCount()} * {@link #getElementCount()}.
- * If a value is negative the following values will be ignored.
- */
- abstract float[] getData();
-
- /**
- * Returns the number of frames to render in the graph.
- */
- abstract int getFrameCount();
-
- /**
- * Returns the number of elements in each frame. This directly affects
- * the number of series drawn in the graph.
- */
- abstract int getElementCount();
-
- /**
- * Returns the current frame, if any. If the returned value is negative
- * the current frame is ignored.
- */
- abstract int getCurrentFrame();
-
- /**
- * Prepares the paint to draw the specified element (or series.)
- */
- abstract void setupGraphPaint(Paint paint, int elementIndex);
-
- /**
- * Prepares the paint to draw the threshold.
- */
- abstract void setupThresholdPaint(Paint paint);
-
- /**
- * Prepares the paint to draw the current frame indicator.
- */
- abstract void setupCurrentFramePaint(Paint paint);
+ public void notifyFramePending() {
}
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7d13399..af16185 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -79,7 +79,7 @@ interface IWindowManager
void removeWindowToken(IBinder token);
void addAppToken(int addPos, IApplicationToken token, int groupId, int stackId,
int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
- int configChanges);
+ int configChanges, boolean voiceInteraction);
void setAppGroupId(IBinder token, int groupId);
void setAppOrientation(IApplicationToken token, int requestedOrientation);
int getAppOrientation(IApplicationToken token);
@@ -120,6 +120,7 @@ interface IWindowManager
boolean isKeyguardSecure();
boolean inKeyguardRestrictedInputMode();
void dismissKeyguard();
+ void keyguardGoingAway();
void closeSystemDialogs(String reason);
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index ae5f37e..358ae8a 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -234,6 +234,14 @@ public final class InputDevice implements Parcelable {
public static final int SOURCE_JOYSTICK = 0x01000000 | SOURCE_CLASS_JOYSTICK;
/**
+ * The input source is a device connected through HDMI-based bus.
+ *
+ * The key comes in through HDMI-CEC or MHL signal line, and is treated as if it were
+ * generated by a locally connected DPAD or keyboard.
+ */
+ public static final int SOURCE_HDMI = 0x02000000 | SOURCE_CLASS_BUTTON;
+
+ /**
* A special input source constant that is used when filtering input devices
* to match devices that provide any type of input source.
*/
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index 5dda934..c5e4c21 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -113,7 +113,7 @@ public final class InputEventConsistencyVerifier {
* @param flags Flags to the verifier, or 0 if none.
*/
public InputEventConsistencyVerifier(Object caller, int flags) {
- this(caller, flags, InputEventConsistencyVerifier.class.getSimpleName());
+ this(caller, flags, null);
}
/**
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 2d1016a..8a996d2 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -639,14 +639,31 @@ public class KeyEvent extends InputEvent implements Parcelable {
* Wakes up the device. Behaves somewhat like {@link #KEYCODE_POWER} but it
* has no effect if the device is already awake. */
public static final int KEYCODE_WAKEUP = 224;
-
- private static final int LAST_KEYCODE = KEYCODE_WAKEUP;
+ /** Key code constant: Pairing key.
+ * Initiates peripheral pairing mode. Useful for pairing remote control
+ * devices or game controllers, especially if no other input mode is
+ * available. */
+ public static final int KEYCODE_PAIRING = 225;
+ /** Key code constant: Media Top Menu key.
+ * Goes to the top of media menu. */
+ public static final int KEYCODE_MEDIA_TOP_MENU = 226;
+ /** Key code constant: '11' key. */
+ public static final int KEYCODE_11 = 227;
+ /** Key code constant: '12' key. */
+ public static final int KEYCODE_12 = 228;
+ /** Key code constant: Last Channel key.
+ * Goes to the last viewed channel. */
+ public static final int KEYCODE_LAST_CHANNEL = 229;
+ /** Key code constant: TV data service key.
+ * Displays data services like weather, sports. */
+ public static final int KEYCODE_TV_DATA_SERVICE = 230;
+
+ private static final int LAST_KEYCODE = KEYCODE_TV_DATA_SERVICE;
// 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/InputEventAttributes.h
- // external/webkit/WebKit/android/plugins/ANPKeyCodes.h
+ // frameworks/native/include/input/InputEventLabels.h
// frameworks/base/core/res/res/values/attrs.xml
// emulator?
// LAST_KEYCODE
@@ -2698,10 +2715,10 @@ public class KeyEvent extends InputEvent implements Parcelable {
public static int keyCodeFromString(String symbolicName) {
if (symbolicName.startsWith(LABEL_PREFIX)) {
symbolicName = symbolicName.substring(LABEL_PREFIX.length());
- }
- int keyCode = nativeKeyCodeFromString(symbolicName);
- if (keyCode > 0) {
- return keyCode;
+ int keyCode = nativeKeyCodeFromString(symbolicName);
+ if (keyCode > 0) {
+ return keyCode;
+ }
}
try {
return Integer.parseInt(symbolicName, 10);
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 0626ab9..7f2defd 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -3070,10 +3070,10 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static int axisFromString(String symbolicName) {
if (symbolicName.startsWith(LABEL_PREFIX)) {
symbolicName = symbolicName.substring(LABEL_PREFIX.length());
- }
- int axis = nativeAxisFromString(symbolicName);
- if (axis >= 0) {
- return axis;
+ int axis = nativeAxisFromString(symbolicName);
+ if (axis >= 0) {
+ return axis;
+ }
}
try {
return Integer.parseInt(symbolicName, 10);
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 0cfde94..e63829e 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -366,10 +366,8 @@ public class RenderNode {
* Deep copies the data into native to simplify reference ownership.
*/
public void setOutline(Outline outline) {
- if (outline == null) {
+ if (outline == null || outline.isEmpty()) {
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);
@@ -387,6 +385,10 @@ public class RenderNode {
nSetClipToOutline(mNativeRenderNode, clipToOutline);
}
+ public boolean getClipToOutline() {
+ return nGetClipToOutline(mNativeRenderNode);
+ }
+
/**
* Controls the RenderNode's circular reveal clip.
*/
@@ -848,6 +850,13 @@ public class RenderNode {
nOutput(mNativeRenderNode);
}
+ /**
+ * Gets the size of the DisplayList for debug purposes.
+ */
+ public int getDebugSize() {
+ return nGetDebugSize(mNativeRenderNode);
+ }
+
///////////////////////////////////////////////////////////////////////////
// Animations
///////////////////////////////////////////////////////////////////////////
@@ -919,6 +928,7 @@ public class RenderNode {
private static native void nSetAnimationMatrix(long renderNode, long animationMatrix);
private static native boolean nHasOverlappingRendering(long renderNode);
+ private static native boolean nGetClipToOutline(long renderNode);
private static native float nGetAlpha(long renderNode);
private static native float nGetLeft(long renderNode);
private static native float nGetTop(long renderNode);
@@ -938,6 +948,7 @@ public class RenderNode {
private static native float nGetPivotX(long renderNode);
private static native float nGetPivotY(long renderNode);
private static native void nOutput(long renderNode);
+ private static native int nGetDebugSize(long renderNode);
///////////////////////////////////////////////////////////////////////////
// Animations
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index a675821..4979059 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -16,18 +16,25 @@
package android.view;
+import android.animation.Animator;
+import android.animation.TimeInterpolator;
import android.graphics.Canvas;
import android.graphics.CanvasProperty;
import android.graphics.Paint;
import android.util.SparseIntArray;
+import com.android.internal.util.VirtualRefBasePtr;
+import com.android.internal.view.animation.FallbackLUTInterpolator;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
/**
* @hide
*/
-public final class RenderNodeAnimator {
-
+public final class RenderNodeAnimator extends Animator {
// Keep in sync with enum RenderProperty in Animator.h
public static final int TRANSLATION_X = 0;
public static final int TRANSLATION_Y = 1;
@@ -41,9 +48,16 @@ public final class RenderNodeAnimator {
public static final int Y = 9;
public static final int Z = 10;
public static final int ALPHA = 11;
+ // The last value in the enum, used for array size initialization
+ public static final int LAST_VALUE = ALPHA;
// Keep in sync with enum PaintFields in Animator.h
public static final int PAINT_STROKE_WIDTH = 0;
+
+ /**
+ * Field for the Paint alpha channel, which should be specified as a value
+ * between 0 and 255.
+ */
public static final int PAINT_ALPHA = 1;
// ViewPropertyAnimator uses a mask for its values, we need to remap them
@@ -65,65 +79,207 @@ public final class RenderNodeAnimator {
put(ViewPropertyAnimator.ALPHA, ALPHA);
}};
- // Keep in sync DeltaValueType in Animator.h
- public static final int DELTA_TYPE_ABSOLUTE = 0;
- public static final int DELTA_TYPE_DELTA = 1;
+ private VirtualRefBasePtr mNativePtr;
private RenderNode mTarget;
- private long mNativePtr;
+ private View mViewTarget;
+ private TimeInterpolator mInterpolator;
- public int mapViewPropertyToRenderProperty(int viewProperty) {
+ private boolean mStarted = false;
+ private boolean mFinished = false;
+
+ public static 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 RenderNodeAnimator(int property, float finalValue) {
+ init(nCreateAnimator(new WeakReference<RenderNodeAnimator>(this),
+ property, finalValue));
}
- public RenderNodeAnimator(CanvasProperty<Float> property, int deltaType, float deltaValue) {
- mNativePtr = nCreateCanvasPropertyFloatAnimator(
+ public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) {
+ init(nCreateCanvasPropertyFloatAnimator(
new WeakReference<RenderNodeAnimator>(this),
- property.getNativeContainer(), deltaType, deltaValue);
+ property.getNativeContainer(), finalValue));
}
- public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField,
- int deltaType, float deltaValue) {
- mNativePtr = nCreateCanvasPropertyPaintAnimator(
+ /**
+ * Creates a new render node animator for a field on a Paint property.
+ *
+ * @param property The paint property to target
+ * @param paintField Paint field to animate, one of {@link #PAINT_ALPHA} or
+ * {@link #PAINT_STROKE_WIDTH}
+ * @param finalValue The target value for the property
+ */
+ public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) {
+ init(nCreateCanvasPropertyPaintAnimator(
new WeakReference<RenderNodeAnimator>(this),
- property.getNativeContainer(), paintField, deltaType, deltaValue);
+ property.getNativeContainer(), paintField, finalValue));
}
- public void start(View target) {
- mTarget = target.mRenderNode;
- mTarget.addAnimator(this);
- // Kick off a frame to start the process
- target.invalidateViewProperty(true, false);
+ private void init(long ptr) {
+ mNativePtr = new VirtualRefBasePtr(ptr);
}
- public void start(Canvas canvas) {
- if (!(canvas instanceof GLES20RecordingCanvas)) {
- throw new IllegalArgumentException("Not a GLES20RecordingCanvas");
+ private void checkMutable() {
+ if (mStarted) {
+ throw new IllegalStateException("Animator has already started, cannot change it now!");
+ }
+ }
+
+ static boolean isNativeInterpolator(TimeInterpolator interpolator) {
+ return interpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class);
+ }
+
+ private void applyInterpolator() {
+ if (mInterpolator == null) return;
+
+ long ni;
+ if (isNativeInterpolator(mInterpolator)) {
+ ni = ((NativeInterpolatorFactory)mInterpolator).createNativeInterpolator();
+ } else {
+ long duration = nGetDuration(mNativePtr.get());
+ ni = FallbackLUTInterpolator.createNativeInterpolator(mInterpolator, duration);
+ }
+ nSetInterpolator(mNativePtr.get(), ni);
+ }
+
+ @Override
+ public void start() {
+ if (mTarget == null) {
+ throw new IllegalStateException("Missing target!");
}
- GLES20RecordingCanvas recordingCanvas = (GLES20RecordingCanvas) canvas;
- mTarget = recordingCanvas.mNode;
+
+ if (mStarted) {
+ throw new IllegalStateException("Already started!");
+ }
+
+ mStarted = true;
+ applyInterpolator();
mTarget.addAnimator(this);
+
+ final ArrayList<AnimatorListener> listeners = getListeners();
+ final int numListeners = listeners == null ? 0 : listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ listeners.get(i).onAnimationStart(this);
+ }
+
+ if (mViewTarget != null) {
+ // Kick off a frame to start the process
+ mViewTarget.invalidateViewProperty(true, false);
+ }
}
+ @Override
public void cancel() {
mTarget.removeAnimator(this);
+
+ final ArrayList<AnimatorListener> listeners = getListeners();
+ final int numListeners = listeners == null ? 0 : listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ listeners.get(i).onAnimationCancel(this);
+ }
}
- public void setDuration(int duration) {
- nSetDuration(mNativePtr, duration);
+ @Override
+ public void end() {
+ throw new UnsupportedOperationException();
}
- long getNativeAnimator() {
- return mNativePtr;
+ @Override
+ public void pause() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void resume() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setTarget(View view) {
+ mViewTarget = view;
+ mTarget = view.mRenderNode;
+ }
+
+ public void setTarget(Canvas canvas) {
+ if (!(canvas instanceof GLES20RecordingCanvas)) {
+ throw new IllegalArgumentException("Not a GLES20RecordingCanvas");
+ }
+
+ final GLES20RecordingCanvas recordingCanvas = (GLES20RecordingCanvas) canvas;
+ setTarget(recordingCanvas.mNode);
+ }
+
+ public void setTarget(RenderNode node) {
+ mViewTarget = null;
+ mTarget = node;
+ }
+
+ public RenderNode getTarget() {
+ return mTarget;
+ }
+
+ /**
+ * WARNING: May only be called once!!!
+ * TODO: Fix above -_-
+ */
+ public void setStartValue(float startValue) {
+ checkMutable();
+ nSetStartValue(mNativePtr.get(), startValue);
+ }
+
+ @Override
+ public void setStartDelay(long startDelay) {
+ checkMutable();
+ nSetStartDelay(mNativePtr.get(), startDelay);
+ }
+
+ @Override
+ public long getStartDelay() {
+ return nGetStartDelay(mNativePtr.get());
+ }
+
+ @Override
+ public RenderNodeAnimator setDuration(long duration) {
+ checkMutable();
+ nSetDuration(mNativePtr.get(), duration);
+ return this;
+ }
+
+ @Override
+ public long getDuration() {
+ return nGetDuration(mNativePtr.get());
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mStarted && !mFinished;
+ }
+
+ @Override
+ public void setInterpolator(TimeInterpolator interpolator) {
+ checkMutable();
+ mInterpolator = interpolator;
+ }
+
+ @Override
+ public TimeInterpolator getInterpolator() {
+ return mInterpolator;
}
private void onFinished() {
+ mFinished = true;
mTarget.removeAnimator(this);
+
+ final ArrayList<AnimatorListener> listeners = getListeners();
+ final int numListeners = listeners == null ? 0 : listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ listeners.get(i).onAnimationEnd(this);
+ }
+ }
+
+ long getNativeAnimator() {
+ return mNativePtr.get();
}
// Called by native
@@ -134,22 +290,16 @@ public final class RenderNodeAnimator {
}
}
- @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);
+ int property, float finalValue);
private static native long nCreateCanvasPropertyFloatAnimator(WeakReference<RenderNodeAnimator> weakThis,
- long canvasProperty, int deltaValueType, float deltaValue);
+ long canvasProperty, float finalValue);
private static native long nCreateCanvasPropertyPaintAnimator(WeakReference<RenderNodeAnimator> weakThis,
- long canvasProperty, int paintField, int deltaValueType, float deltaValue);
- private static native void nSetDuration(long nativePtr, int duration);
- private static native void nUnref(long nativePtr);
+ long canvasProperty, int paintField, float finalValue);
+ private static native void nSetStartValue(long nativePtr, float startValue);
+ private static native void nSetDuration(long nativePtr, long duration);
+ private static native long nGetDuration(long nativePtr);
+ private static native void nSetStartDelay(long nativePtr, long startDelay);
+ private static native long nGetStartDelay(long nativePtr);
+ private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c15ce44..5cd3d62 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -38,11 +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,
- boolean useIdentityTransform);
+ Rect sourceCrop, 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,
- boolean useIdentityTransform);
+ Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
+ boolean allLayers, boolean useIdentityTransform);
private static native void nativeOpenTransaction();
private static native void nativeCloseTransaction();
@@ -597,8 +597,8 @@ public class SurfaceControl {
public static void screenshot(IBinder display, Surface consumer,
int width, int height, int minLayer, int maxLayer,
boolean useIdentityTransform) {
- screenshot(display, consumer, width, height, minLayer, maxLayer, false,
- useIdentityTransform);
+ screenshot(display, consumer, new Rect(), width, height, minLayer, maxLayer,
+ false, useIdentityTransform);
}
/**
@@ -613,7 +613,7 @@ public class SurfaceControl {
*/
public static void screenshot(IBinder display, Surface consumer,
int width, int height) {
- screenshot(display, consumer, width, height, 0, 0, true, false);
+ screenshot(display, consumer, new Rect(), width, height, 0, 0, true, false);
}
/**
@@ -623,7 +623,7 @@ public class SurfaceControl {
* @param consumer The {@link Surface} to take the screenshot into.
*/
public static void screenshot(IBinder display, Surface consumer) {
- screenshot(display, consumer, 0, 0, 0, 0, true, false);
+ screenshot(display, consumer, new Rect(), 0, 0, 0, 0, true, false);
}
/**
@@ -634,6 +634,8 @@ public class SurfaceControl {
* the versions that use a {@link Surface} instead, such as
* {@link SurfaceControl#screenshot(IBinder, Surface)}.
*
+ * @param sourceCrop The portion of the screen to capture into the Bitmap;
+ * caller may pass in 'new Rect()' if no cropping is desired.
* @param width The desired width of the returned bitmap; the raw
* screen will be scaled down to this size.
* @param height The desired height of the returned bitmap; the raw
@@ -649,13 +651,13 @@ public class SurfaceControl {
* 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,
- boolean useIdentityTransform) {
+ public static Bitmap screenshot(Rect sourceCrop, 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,
- useIdentityTransform);
+ return nativeScreenshot(displayToken, sourceCrop, width, height,
+ minLayer, maxLayer, false, useIdentityTransform);
}
/**
@@ -674,10 +676,10 @@ 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, false);
+ return nativeScreenshot(displayToken, new Rect(), width, height, 0, 0, true, false);
}
- private static void screenshot(IBinder display, Surface consumer,
+ private static void screenshot(IBinder display, Surface consumer, Rect sourceCrop,
int width, int height, int minLayer, int maxLayer, boolean allLayers,
boolean useIdentityTransform) {
if (display == null) {
@@ -686,7 +688,7 @@ public class SurfaceControl {
if (consumer == null) {
throw new IllegalArgumentException("consumer must not be null");
}
- nativeScreenshot(display, consumer, width, height, minLayer, maxLayer, allLayers,
- useIdentityTransform);
+ nativeScreenshot(display, consumer, sourceCrop, width, height,
+ minLayer, maxLayer, allLayers, useIdentityTransform);
}
}
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 1765c43..2a9f7d5 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -405,7 +405,9 @@ public class TextureView extends View {
// To cancel updates, the easiest thing to do is simply to remove the
// updates listener
if (visibility == VISIBLE) {
- mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
+ if (mLayer != null) {
+ mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
+ }
updateLayerAndInvalidate();
} else {
mSurface.setOnFrameAvailableListener(null);
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 2587ba1..9b3ef7f 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -19,17 +19,22 @@ package android.view;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.Trace;
+import android.util.Log;
+import android.util.TimeUtils;
import android.view.Surface.OutOfResourcesException;
import android.view.View.AttachInfo;
+import java.io.FileDescriptor;
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.
@@ -51,21 +56,26 @@ public class ThreadedRenderer extends HardwareRenderer {
private static final Rect NULL_RECT = new Rect();
- private static final long NANOS_PER_MS = 1000000;
-
// Keep in sync with DrawFrameTask.h SYNC_* flags
// Nothing interesting to report
private static final int SYNC_OK = 0x0;
// Needs a ViewRoot invalidate
private static final int SYNC_INVALIDATE_REQUIRED = 0x1;
+ private static final String[] VISUALIZERS = {
+ PROFILE_PROPERTY_VISUALIZE_BARS,
+ };
+
private int mWidth, mHeight;
private long mNativeProxy;
private boolean mInitialized = false;
private RenderNode mRootNode;
private Choreographer mChoreographer;
+ private boolean mProfilingEnabled;
ThreadedRenderer(boolean translucent) {
+ AtlasInitializer.sInstance.init();
+
long rootNodePtr = nCreateRootRenderNode();
mRootNode = RenderNode.adopt(rootNodePtr);
mRootNode.setClipToBounds(false);
@@ -74,6 +84,8 @@ public class ThreadedRenderer extends HardwareRenderer {
// Setup timing
mChoreographer = Choreographer.getInstance();
nSetFrameInterval(mNativeProxy, mChoreographer.getFrameIntervalNanos());
+
+ loadSystemProperties();
}
@Override
@@ -112,7 +124,7 @@ public class ThreadedRenderer extends HardwareRenderer {
@Override
void destroyHardwareResources(View view) {
destroyResources(view);
- // TODO: GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
+ nFlushCaches(mNativeProxy, GLES20Canvas.FLUSH_CACHES_LAYERS);
}
private static void destroyResources(View view) {
@@ -140,11 +152,11 @@ public class ThreadedRenderer extends HardwareRenderer {
}
@Override
- void setup(int width, int height) {
+ void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius) {
mWidth = width;
mHeight = height;
mRootNode.setLeftTopRightBottom(0, 0, mWidth, mHeight);
- nSetup(mNativeProxy, width, height);
+ nSetup(mNativeProxy, width, height, lightX, lightY, lightZ, lightRadius);
}
@Override
@@ -163,19 +175,33 @@ public class ThreadedRenderer extends HardwareRenderer {
}
@Override
- void dumpGfxInfo(PrintWriter pw) {
- // TODO Auto-generated method stub
+ void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) {
+ pw.flush();
+ nDumpProfileInfo(mNativeProxy, fd);
}
- @Override
- long getFrameCount() {
- // TODO Auto-generated method stub
- return 0;
+ 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;
+ }
+
+ private static boolean checkIfProfilingRequested() {
+ String profiling = SystemProperties.get(HardwareRenderer.PROFILE_PROPERTY);
+ int graphType = search(VISUALIZERS, profiling);
+ return (graphType >= 0) || Boolean.parseBoolean(profiling);
}
@Override
boolean loadSystemProperties() {
- return nLoadSystemProperties(mNativeProxy);
+ boolean changed = nLoadSystemProperties(mNativeProxy);
+ boolean wantProfiling = checkIfProfilingRequested();
+ if (wantProfiling != mProfilingEnabled) {
+ mProfilingEnabled = wantProfiling;
+ changed = true;
+ }
+ return changed;
}
private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
@@ -188,9 +214,11 @@ public class ThreadedRenderer extends HardwareRenderer {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
HardwareCanvas canvas = mRootNode.start(mWidth, mHeight);
try {
- callbacks.onHardwarePostDraw(canvas);
+ canvas.save();
+ callbacks.onHardwarePreDraw(canvas);
canvas.drawDisplayList(view.getDisplayList());
callbacks.onHardwarePostDraw(canvas);
+ canvas.restore();
} finally {
mRootNode.end(canvas);
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
@@ -203,16 +231,26 @@ public class ThreadedRenderer extends HardwareRenderer {
void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) {
attachInfo.mIgnoreDirtyState = true;
long frameTimeNanos = mChoreographer.getFrameTimeNanos();
- attachInfo.mDrawingTime = frameTimeNanos / NANOS_PER_MS;
+ attachInfo.mDrawingTime = frameTimeNanos / TimeUtils.NANOS_PER_MS;
+
+ long recordDuration = 0;
+ if (mProfilingEnabled) {
+ recordDuration = System.nanoTime();
+ }
updateRootDisplayList(view, callbacks);
+ if (mProfilingEnabled) {
+ recordDuration = System.nanoTime() - recordDuration;
+ }
+
attachInfo.mIgnoreDirtyState = false;
if (dirty == null) {
dirty = NULL_RECT;
}
int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,
+ recordDuration, view.getResources().getDisplayMetrics().density,
dirty.left, dirty.top, dirty.right, dirty.bottom);
if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) {
attachInfo.mViewRootImpl.invalidate();
@@ -256,12 +294,7 @@ public class ThreadedRenderer extends HardwareRenderer {
@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?
+ nPushLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater());
}
@Override
@@ -271,7 +304,7 @@ public class ThreadedRenderer extends HardwareRenderer {
@Override
void onLayerDestroyed(HardwareLayer layer) {
- nDestroyLayer(mNativeProxy, layer.getDeferredLayerUpdater());
+ nCancelLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater());
}
@Override
@@ -284,6 +317,11 @@ public class ThreadedRenderer extends HardwareRenderer {
}
@Override
+ public void notifyFramePending() {
+ nNotifyFramePending(mNativeProxy);
+ }
+
+ @Override
protected void finalize() throws Throwable {
try {
nDeleteProxy(mNativeProxy);
@@ -293,8 +331,53 @@ public class ThreadedRenderer extends HardwareRenderer {
}
}
- /** @hide */
- public static native void postToRenderThread(Runnable runnable);
+ static void startTrimMemory(int level) {
+ // TODO
+ }
+
+ static void endTrimMemory() {
+ // TODO
+ }
+
+ private static class AtlasInitializer {
+ static AtlasInitializer sInstance = new AtlasInitializer();
+
+ private boolean mInitialized = false;
+
+ private AtlasInitializer() {}
+
+ synchronized void init() {
+ if (mInitialized) return;
+ 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) {
+ nSetAtlas(buffer, map);
+ mInitialized = true;
+ }
+ // 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);
+ }
+ }
+ }
+
+ static native void setupShadersDiskCache(String cacheFile);
+
+ private static native void nSetAtlas(GraphicBuffer buffer, long[] map);
private static native long nCreateRootRenderNode();
private static native long nCreateProxy(boolean translucent, long rootRenderNode);
@@ -306,11 +389,11 @@ public class ThreadedRenderer extends HardwareRenderer {
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 nSetup(long nativeProxy, int width, int height,
+ float lightX, float lightY, float lightZ, float lightRadius);
private static native void nSetOpaque(long nativeProxy, boolean opaque);
- private static native void nSetDisplayListData(long nativeProxy, long displayList,
- long newData);
- private static native int nSyncAndDrawFrame(long nativeProxy, long frameTimeNanos,
+ private static native int nSyncAndDrawFrame(long nativeProxy,
+ long frameTimeNanos, long recordDuration, float density,
int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom);
private static native void nRunWithGlContext(long nativeProxy, Runnable runnable);
private static native void nDestroyCanvasAndSurface(long nativeProxy);
@@ -320,7 +403,13 @@ public class ThreadedRenderer extends HardwareRenderer {
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 nPushLayerUpdate(long nativeProxy, long layer);
+ private static native void nCancelLayerUpdate(long nativeProxy, long layer);
+
+ private static native void nFlushCaches(long nativeProxy, int flushMode);
private static native void nFence(long nativeProxy);
+ private static native void nNotifyFramePending(long nativeProxy);
+
+ private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index bef96b1..622fa8c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -670,7 +670,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* @attr ref android.R.styleable#View_scrollbarAlwaysDrawHorizontalTrack
* @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack
* @attr ref android.R.styleable#View_stateListAnimator
- * @attr ref android.R.styleable#View_sharedElementName
+ * @attr ref android.R.styleable#View_viewName
* @attr ref android.R.styleable#View_soundEffectsEnabled
* @attr ref android.R.styleable#View_tag
* @attr ref android.R.styleable#View_textAlignment
@@ -2774,8 +2774,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* @hide
+ *
+ * Makes system ui transparent.
+ */
+ public static final int SYSTEM_UI_TRANSPARENT = 0x00008000;
+
+ /**
+ * @hide
*/
- public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x0000FFFF;
+ public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x00007FFF;
/**
* These are the system UI flags that can be cleared by events outside
@@ -3185,6 +3192,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private int mBackgroundResource;
private boolean mBackgroundSizeChanged;
+ private String mViewName;
+
static class ListenerInfo {
/**
* Listener used to dispatch focus change events.
@@ -3997,8 +4006,8 @@ 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));
+ case R.styleable.View_viewName:
+ setViewName(a.getString(attr));
break;
case R.styleable.View_nestedScrollingEnabled:
setNestedScrollingEnabled(a.getBoolean(attr, false));
@@ -4765,8 +4774,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
}
- manageFocusHotspot(true, oldFocus);
onFocusChanged(true, direction, previouslyFocusedRect);
+ manageFocusHotspot(true, oldFocus);
refreshDrawableState();
}
}
@@ -4780,25 +4789,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @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 (mBackground == null) {
+ return;
+ }
- if (!focused) {
- mBackground.removeHotspot(R.attr.state_focused);
- }
+ 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(x, y);
}
/**
@@ -6743,6 +6750,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Sets the pressed state for this view and provides a touch coordinate for
+ * animation hinting.
+ *
+ * @param pressed Pass true to set the View's internal state to "pressed",
+ * or false to reverts the View's internal state from a
+ * previously set "pressed" state.
+ * @param x The x coordinate of the touch that caused the press
+ * @param y The y coordinate of the touch that caused the press
+ */
+ private void setPressed(boolean pressed, float x, float y) {
+ if (pressed) {
+ setHotspot(x, y);
+ }
+
+ setPressed(pressed);
+ }
+
+ /**
* Sets the pressed state for this view.
*
* @see #isClickable()
@@ -7145,6 +7170,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (viewRootImpl != null) {
viewRootImpl.setAccessibilityFocus(this, null);
}
+ Rect rect = (mAttachInfo != null) ? mAttachInfo.mTmpInvalRect : new Rect();
+ getDrawingRect(rect);
+ requestRectangleOnScreen(rect, false);
invalidate();
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
return true;
@@ -8981,7 +9009,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
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
@@ -9014,8 +9041,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);
+ setPressed(true, x, y);
}
if (!mHasPerformedLongPress) {
@@ -9049,8 +9075,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
removeTapCallback();
- } else {
- clearHotspot(R.attr.state_pressed);
}
break;
@@ -9076,21 +9100,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
- setHotspot(R.attr.state_pressed, x, y);
+ setHotspot(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:
- setHotspot(R.attr.state_pressed, x, y);
+ setHotspot(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
@@ -9112,17 +9135,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
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);
+ private void setHotspot(float x, float y) {
+ if (mBackground != null) {
+ mBackground.setHotspot(x, y);
}
}
@@ -9163,7 +9178,6 @@ 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);
}
@@ -9222,6 +9236,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Request unbuffered dispatch of the given stream of MotionEvents to this View.
+ *
+ * Until this View receives a corresponding {@link MotionEvent#ACTION_UP}, ask that the input
+ * system not batch {@link MotionEvent}s but instead deliver them as soon as they're
+ * available. This method should only be called for touch events.
+ *
+ * <p class="note">This api is not intended for most applications. Buffered dispatch
+ * provides many of benefits, and just requesting unbuffered dispatch on most MotionEvent
+ * streams will not improve your input latency. Side effects include: increased latency,
+ * jittery scrolls and inability to take advantage of system resampling. Talk to your input
+ * professional to see if {@link #requestUnbufferedDispatch(MotionEvent)} is right for
+ * you.</p>
+ */
+ public final void requestUnbufferedDispatch(MotionEvent event) {
+ final int action = event.getAction();
+ if (mAttachInfo == null
+ || action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_MOVE
+ || !event.isTouchEvent()) {
+ return;
+ }
+ mAttachInfo.mUnbufferedDispatchRequested = true;
+ }
+
+ /**
* Set flags controlling behavior of this view.
*
* @param flags Constant indicating the value which should be set
@@ -10666,24 +10704,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Sets the outline of the view, which defines the shape of the shadow it
- * casts.
+ * Sets the {@link Outline} of the view, which defines the shape of the shadow it
+ * casts, and enables outline clipping.
+ * <p>
+ * By default, a View queries its Outline from its background drawable, via
+ * {@link Drawable#getOutline(Outline)}. Manually setting the Outline with this method allows
+ * this behavior to be overridden.
* <p>
- * If the outline is not set or is null, shadows will be cast from the
- * bounds of the View.
+ * If the outline is {@link Outline#isEmpty()} or is <code>null</code>,
+ * shadows will not be cast.
+ * <p>
+ * Only outlines that return true from {@link Outline#canClip()} may be used for clipping.
*
* @param outline The new outline of the view.
- * Must be {@link android.graphics.Outline#isValid() valid.}
+ *
+ * @see #setClipToOutline(boolean)
+ * @see #getClipToOutline()
*/
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;
+ if (outline == null || outline.isEmpty()) {
+ if (mOutline != null) {
+ mOutline.setEmpty();
+ }
} else {
// always copy the path since caller may reuse
if (mOutline == null) {
@@ -10694,9 +10738,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mRenderNode.setOutline(mOutline);
}
- // TODO: remove
- public final boolean getClipToOutline() { return false; }
- public void setClipToOutline(boolean clipToOutline) {}
+ /**
+ * Returns whether the Outline should be used to clip the contents of the View.
+ * <p>
+ * Note that this flag will only be respected if the View's Outline returns true from
+ * {@link Outline#canClip()}.
+ *
+ * @see #setOutline(Outline)
+ * @see #setClipToOutline(boolean)
+ */
+ public final boolean getClipToOutline() {
+ return mRenderNode.getClipToOutline();
+ }
+
+ /**
+ * Sets whether the View's Outline should be used to clip the contents of the View.
+ * <p>
+ * Note that this flag will only be respected if the View's Outline returns true from
+ * {@link Outline#canClip()}.
+ *
+ * @see #setOutline(Outline)
+ * @see #getClipToOutline()
+ */
+ public void setClipToOutline(boolean clipToOutline) {
+ damageInParent();
+ if (getClipToOutline() != clipToOutline) {
+ mRenderNode.setClipToOutline(clipToOutline);
+ }
+ }
private void queryOutlineFromBackgroundIfUndefined() {
if ((mPrivateFlags3 & PFLAG3_OUTLINE_DEFINED) == 0) {
@@ -10705,10 +10774,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mOutline = new Outline();
} else {
//invalidate outline, to ensure background calculates it
- mOutline.set(null);
+ mOutline.setEmpty();
}
if (mBackground.getOutline(mOutline)) {
- if (!mOutline.isValid()) {
+ if (mOutline.isEmpty()) {
throw new IllegalStateException("Background drawable failed to build outline");
}
mRenderNode.setOutline(mOutline);
@@ -12867,10 +12936,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
- if (mBackground != null) {
- mBackground.clearHotspots();
- }
-
removeUnsetPressCallback();
removeLongPressCallback();
removePerformClickCallback();
@@ -13528,12 +13593,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
- // The layer is not valid if the underlying GPU resources cannot be allocated
- mHardwareLayer.flushChanges();
- if (!mHardwareLayer.isValid()) {
- return null;
- }
-
mHardwareLayer.setLayerPaint(mLayerPaint);
RenderNode displayList = mHardwareLayer.startRecording();
updateDisplayListIfDirty(displayList, true);
@@ -16132,6 +16191,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Set this view's optical insets.
+ *
+ * <p>This method should be treated similarly to setMeasuredDimension and not as a general
+ * property. Views that compute their own optical insets should call it as part of measurement.
+ * This method does not request layout. If you are setting optical insets outside of
+ * measure/layout itself you will want to call requestLayout() yourself.
+ * </p>
+ * @hide
+ */
+ public void setOpticalInsets(Insets insets) {
+ mLayoutInsets = insets;
+ }
+
+ /**
* Changes the selection state of this view. A view can be selected or not.
* Note that selection is not the same as focus. Views are typically
* selected in the context of an AdapterView like ListView or GridView;
@@ -18839,15 +18912,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * 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.
+ * Adds all Views that have {@link #getViewName()} non-null to namedElements.
+ * @param namedElements Will contain all Views in the hierarchy having a view name.
* @hide
*/
- public void findSharedElements(Map<String, View> sharedElements) {
+ public void findNamedViews(Map<String, View> namedElements) {
if (getVisibility() == VISIBLE) {
- String sharedElementName = getSharedElementName();
- if (sharedElementName != null) {
- sharedElements.put(sharedElementName, this);
+ String viewName = getViewName();
+ if (viewName != null) {
+ namedElements.put(viewName, this);
}
}
}
@@ -19215,8 +19288,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@Override
public void run() {
mPrivateFlags &= ~PFLAG_PREPRESSED;
- setHotspot(R.attr.state_pressed, x, y);
- setPressed(true);
+ setPressed(true, x, y);
checkForLongClick(ViewConfiguration.getTapTimeout());
}
}
@@ -19247,32 +19319,26 @@ 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.
+ * Sets the name of the View to be used to identify Views in Transitions.
+ * 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)
+ * @param viewName The name of the View to uniquely identify it for Transitions.
*/
- public void setSharedElementName(String sharedElementName) {
- setTagInternal(com.android.internal.R.id.shared_element_name, sharedElementName);
+ public final void setViewName(String viewName) {
+ mViewName = viewName;
}
/**
- * 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.
+ * Returns the name of the View to be used to identify Views in Transitions.
+ * 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>
+ * <p>This returns null if the View has not been given a name.</p>
*
- * @return The name used for this View for cross-Activity transitions or null if
- * this View has not been identified as shared.
+ * @return The name used of the View to be used to identify Views in Transitions or null
+ * if no name has been given.
*/
- public String getSharedElementName() {
- return (String) getTag(com.android.internal.R.id.shared_element_name);
+ public String getViewName() {
+ return mViewName;
}
/**
@@ -19503,7 +19569,6 @@ 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);
}
}
@@ -19727,6 +19792,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
boolean mInTouchMode;
/**
+ * Indicates whether the view has requested unbuffered input dispatching for the current
+ * event stream.
+ */
+ boolean mUnbufferedDispatchRequested;
+
+ /**
* Indicates that ViewAncestor should trigger a global layout change
* the next time it performs a traversal
*/
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 4309366..0f40ee7 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -456,6 +456,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// views during a transition when they otherwise would have become gone/invisible
private ArrayList<View> mVisibilityChangingChildren;
+ // Temporary holder of presorted children, only used for
+ // input/software draw dispatch for correctly Z ordering.
+ private ArrayList<View> mPreSortedChildren;
+
// Indicates how many of this container's child subtrees contain transient state
@ViewDebug.ExportedProperty(category = "layout")
private int mChildCountWithTransientState = 0;
@@ -1499,13 +1503,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final float y = event.getY();
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
- final boolean customChildOrder = isChildrenDrawingOrderEnabled();
+ final ArrayList<View> preorderedList = buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
HoverTarget lastHoverTarget = null;
for (int i = childrenCount - 1; i >= 0; i--) {
- final int childIndex = customChildOrder
- ? getChildDrawingOrder(childrenCount, i) : i;
- final View child = children[childIndex];
+ int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
+ final View child = (preorderedList == null)
+ ? children[childIndex] : preorderedList.get(childIndex);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
@@ -1572,6 +1578,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
break;
}
}
+ if (preorderedList != null) preorderedList.clear();
}
}
@@ -1778,23 +1785,28 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// Send the event to the child under the pointer.
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
- final View[] children = mChildren;
final float x = event.getX();
final float y = event.getY();
- final boolean customOrder = isChildrenDrawingOrderEnabled();
+ final ArrayList<View> preorderedList = buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
+ final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
- final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
- final View child = children[childIndex];
+ int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
+ final View child = (preorderedList == null)
+ ? children[childIndex] : preorderedList.get(childIndex);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
if (dispatchTransformedGenericPointerEvent(event, child)) {
+ if (preorderedList != null) preorderedList.clear();
return true;
}
}
+ if (preorderedList != null) preorderedList.clear();
}
// No child handled the event. Send it to this view group.
@@ -1910,13 +1922,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
+ final ArrayList<View> preorderedList = buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
-
- final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
- final int childIndex = customOrder ?
- getChildDrawingOrder(childrenCount, i) : i;
- final View child = children[childIndex];
+ final int childIndex = customOrder
+ ? getChildDrawingOrder(childrenCount, i) : i;
+ final View child = (preorderedList == null)
+ ? children[childIndex] : preorderedList.get(childIndex);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
@@ -1934,7 +1948,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
- mLastTouchDownIndex = childIndex;
+ if (preorderedList != null) {
+ // childIndex points into presorted list, find original index
+ for (int j = 0; j < childrenCount; j++) {
+ if (children[childIndex] == mChildren[j]) {
+ mLastTouchDownIndex = j;
+ break;
+ }
+ }
+ } else {
+ mLastTouchDownIndex = childIndex;
+ }
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
@@ -1942,6 +1966,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
break;
}
}
+ if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
@@ -2301,13 +2326,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* 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.
+ * is not null or if {@link #getViewName()} 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;
+ return getBackground() != null || getViewName() != null;
}
}
@@ -2318,8 +2343,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* 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)
+ * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.app.Activity,
+ * android.util.Pair[])
*/
public void setTransitionGroup(boolean isTransitionGroup) {
mGroupFlags |= FLAG_IS_TRANSITION_GROUP_SET;
@@ -2928,7 +2953,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
protected void dispatchDraw(Canvas canvas) {
- final int count = mChildrenCount;
+ final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
@@ -2936,15 +2961,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;
final boolean buildCache = !isHardwareAccelerated();
- for (int i = 0; i < count; i++) {
+ for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();
- attachLayoutAnimationParameters(child, params, i, count);
+ attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
if (cache) {
child.setDrawingCacheEnabled(true);
- if (buildCache) {
+ if (buildCache) {
child.buildDrawingCache(true);
}
}
@@ -2997,21 +3022,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
boolean more = false;
final long drawingTime = getDrawingTime();
- if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
- for (int i = 0; i < count; i++) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
- more |= drawChild(canvas, child, drawingTime);
- }
- }
- } else {
- for (int i = 0; i < count; i++) {
- final View child = children[getChildDrawingOrder(count, i)];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
- more |= drawChild(canvas, child, drawingTime);
- }
+
+ // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
+ // draw reordering internally
+ final ArrayList<View> preorderedList = canvas.isHardwareAccelerated()
+ ? null : buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
+ for (int i = 0; i < childrenCount; i++) {
+ int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
+ final View child = (preorderedList == null)
+ ? children[childIndex] : preorderedList.get(childIndex);
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
+ more |= drawChild(canvas, child, drawingTime);
}
}
+ if (preorderedList != null) preorderedList.clear();
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
@@ -3096,6 +3122,47 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return i;
}
+ private boolean hasChildWithZ() {
+ for (int i = 0; i < mChildrenCount; i++) {
+ if (mChildren[i].getZ() != 0) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Populates (and returns) mPreSortedChildren with a pre-ordered list of the View's children,
+ * sorted first by Z, then by child drawing order (if applicable).
+ *
+ * Uses a stable, insertion sort which is commonly O(n) for ViewGroups with very few elevated
+ * children.
+ */
+ private ArrayList<View> buildOrderedChildList() {
+ final int count = mChildrenCount;
+ if (count <= 1 || !hasChildWithZ()) return null;
+
+ if (mPreSortedChildren == null) {
+ mPreSortedChildren = new ArrayList<View>(count);
+ } else {
+ mPreSortedChildren.ensureCapacity(count);
+ }
+
+ final boolean useCustomOrder = isChildrenDrawingOrderEnabled();
+ for (int i = 0; i < mChildrenCount; i++) {
+ // add next child (in child order) to end of list
+ int childIndex = useCustomOrder ? getChildDrawingOrder(mChildrenCount, i) : i;
+ View nextChild = mChildren[childIndex];
+ float currentZ = nextChild.getZ();
+
+ // insert ahead of any Views with greater Z
+ int insertIndex = i;
+ while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
+ insertIndex--;
+ }
+ mPreSortedChildren.add(insertIndex, nextChild);
+ }
+ return mPreSortedChildren;
+ }
+
private void notifyAnimationListener() {
mGroupFlags &= ~FLAG_NOTIFY_ANIMATION_LISTENER;
mGroupFlags |= FLAG_ANIMATION_DONE;
@@ -5956,15 +6023,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/** @hide */
@Override
- public void findSharedElements(Map<String, View> sharedElements) {
+ public void findNamedViews(Map<String, View> namedElements) {
if (getVisibility() != VISIBLE) {
return;
}
- super.findSharedElements(sharedElements);
+ super.findNamedViews(namedElements);
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
- child.findSharedElements(sharedElements);
+ child.findNamedViews(namedElements);
}
}
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 11d2622..af1de78 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -19,6 +19,7 @@ package android.view;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.animation.TimeInterpolator;
+import android.os.Build;
import java.util.ArrayList;
import java.util.HashMap;
@@ -109,6 +110,11 @@ public class ViewPropertyAnimator {
private ValueAnimator mTempValueAnimator;
/**
+ * A RenderThread-driven backend that may intercept startAnimation
+ */
+ private ViewPropertyAnimatorRT mRTBackend;
+
+ /**
* This listener is the mechanism by which the underlying Animator causes changes to the
* properties currently being animated, as well as the cleanup after an animation is
* complete.
@@ -227,7 +233,7 @@ public class ViewPropertyAnimator {
* values are used to calculate the animated value for a given animation fraction
* during the animation.
*/
- private static class NameValuesHolder {
+ static class NameValuesHolder {
int mNameConstant;
float mFromValue;
float mDeltaValue;
@@ -247,6 +253,10 @@ public class ViewPropertyAnimator {
ViewPropertyAnimator(View view) {
mView = view;
view.ensureTransformationInfo();
+ // TODO: Disabled because of b/15287046
+ //if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) {
+ // mRTBackend = new ViewPropertyAnimatorRT(view);
+ //}
}
/**
@@ -371,6 +381,10 @@ public class ViewPropertyAnimator {
return this;
}
+ Animator.AnimatorListener getListener() {
+ return mListener;
+ }
+
/**
* Sets a listener for update events in the underlying ValueAnimator that runs
* the property animations. Note that the underlying animator is animating between
@@ -390,6 +404,10 @@ public class ViewPropertyAnimator {
return this;
}
+ ValueAnimator.AnimatorUpdateListener getUpdateListener() {
+ return mUpdateListener;
+ }
+
/**
* Starts the currently pending property animations immediately. Calling <code>start()</code>
* is optional because all animations start automatically at the next opportunity. However,
@@ -825,12 +843,22 @@ public class ViewPropertyAnimator {
return this;
}
+ boolean hasActions() {
+ return mPendingSetupAction != null
+ || mPendingCleanupAction != null
+ || mPendingOnStartAction != null
+ || mPendingOnEndAction != null;
+ }
+
/**
* Starts the underlying Animator for a set of properties. We use a single animator that
* simply runs from 0 to 1, and then use that fractional value to set each property
* value accordingly.
*/
private void startAnimation() {
+ if (mRTBackend != null && mRTBackend.startAnimation(this)) {
+ return;
+ }
mView.setHasTransientState(true);
ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
ArrayList<NameValuesHolder> nameValueList =
@@ -1115,7 +1143,8 @@ public class ViewPropertyAnimator {
// Shouldn't happen, but just to play it safe
return;
}
- boolean useRenderNodeProperties = mView.mRenderNode != null;
+
+ boolean hardwareAccelerated = mView.isHardwareAccelerated();
// alpha requires slightly different treatment than the other (transform) properties.
// The logic in setAlpha() is not simply setting mAlpha, plus the invalidation
@@ -1123,13 +1152,13 @@ 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 (!useRenderNodeProperties) {
+ if (!hardwareAccelerated) {
mView.invalidateParentCaches();
}
float fraction = animation.getAnimatedFraction();
int propertyMask = propertyBundle.mPropertyMask;
if ((propertyMask & TRANSFORM_MASK) != 0) {
- mView.invalidateViewProperty(false, false);
+ mView.invalidateViewProperty(hardwareAccelerated, false);
}
ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
if (valueList != null) {
@@ -1145,7 +1174,7 @@ public class ViewPropertyAnimator {
}
}
if ((propertyMask & TRANSFORM_MASK) != 0) {
- if (!useRenderNodeProperties) {
+ if (!hardwareAccelerated) {
mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation
}
}
diff --git a/core/java/android/view/ViewPropertyAnimatorRT.java b/core/java/android/view/ViewPropertyAnimatorRT.java
new file mode 100644
index 0000000..709efdb
--- /dev/null
+++ b/core/java/android/view/ViewPropertyAnimatorRT.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.animation.TimeInterpolator;
+import android.view.ViewPropertyAnimator.NameValuesHolder;
+
+import com.android.internal.view.animation.FallbackLUTInterpolator;
+
+import java.util.ArrayList;
+
+
+/**
+ * This is a RenderThread driven backend for ViewPropertyAnimator.
+ */
+class ViewPropertyAnimatorRT {
+
+ private final View mView;
+
+ private RenderNodeAnimator mAnimators[] = new RenderNodeAnimator[RenderNodeAnimator.LAST_VALUE + 1];
+
+ ViewPropertyAnimatorRT(View view) {
+ mView = view;
+ }
+
+ /**
+ * @return true if ViewPropertyAnimatorRT handled the animation,
+ * false if ViewPropertyAnimator needs to handle it
+ */
+ public boolean startAnimation(ViewPropertyAnimator parent) {
+ cancelAnimators(parent.mPendingAnimations);
+ if (!canHandleAnimator(parent)) {
+ return false;
+ }
+ doStartAnimation(parent);
+ return true;
+ }
+
+ private void doStartAnimation(ViewPropertyAnimator parent) {
+ int size = parent.mPendingAnimations.size();
+
+ long startDelay = parent.getStartDelay();
+ long duration = parent.getDuration();
+ TimeInterpolator interpolator = parent.getInterpolator();
+ if (!RenderNodeAnimator.isNativeInterpolator(interpolator)) {
+ interpolator = new FallbackLUTInterpolator(interpolator, duration);
+ }
+ for (int i = 0; i < size; i++) {
+ NameValuesHolder holder = parent.mPendingAnimations.get(i);
+ int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant);
+
+ RenderNodeAnimator animator = new RenderNodeAnimator(property, holder.mFromValue + holder.mDeltaValue);
+ animator.setStartDelay(startDelay);
+ animator.setDuration(duration);
+ animator.setInterpolator(interpolator);
+ animator.setTarget(mView);
+ animator.start();
+ }
+
+ parent.mPendingAnimations.clear();
+ }
+
+ private boolean canHandleAnimator(ViewPropertyAnimator parent) {
+ // TODO: Can we eliminate this entirely?
+ // If RenderNode.animatorProperties() can be toggled to point at staging
+ // instead then RNA can be used as the animators for software as well
+ // as the updateListener fallback paths. If this can be toggled
+ // at the top level somehow, combined with requiresUiRedraw, we could
+ // ensure that RT does not self-animate, allowing for safe driving of
+ // the animators from the UI thread using the same mechanisms
+ // ViewPropertyAnimator does, just with everything sitting on a single
+ // animator subsystem instead of multiple.
+
+ if (parent.getUpdateListener() != null) {
+ return false;
+ }
+ if (parent.getListener() != null) {
+ // TODO support
+ return false;
+ }
+ if (!mView.isHardwareAccelerated()) {
+ // TODO handle this maybe?
+ return false;
+ }
+ if (parent.hasActions()) {
+ return false;
+ }
+ // Here goes nothing...
+ return true;
+ }
+
+ private void cancelAnimators(ArrayList<NameValuesHolder> mPendingAnimations) {
+ int size = mPendingAnimations.size();
+ for (int i = 0; i < size; i++) {
+ NameValuesHolder holder = mPendingAnimations.get(i);
+ int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant);
+ if (mAnimators[property] != null) {
+ mAnimators[property].cancel();
+ mAnimators[property] = null;
+ }
+ }
+ }
+
+}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9b09d85..f3d1e3c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -47,7 +47,6 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
-import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -111,6 +110,7 @@ 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_STAGES = false || LOCAL_LOGV;
/**
* Set this system property to true to force the view hierarchy to render
@@ -230,6 +230,7 @@ public final class ViewRootImpl implements ViewParent,
QueuedInputEvent mPendingInputEventTail;
int mPendingInputEventCount;
boolean mProcessInputEventsScheduled;
+ boolean mUnbufferedInputDispatch;
String mPendingInputEventQueueLengthCounterName = "pq";
InputStage mFirstInputStage;
@@ -668,6 +669,11 @@ public final class ViewRootImpl implements ViewParent,
public void detachFunctor(long functor) {
// TODO: Make the resize buffer some other way to not need this block
mBlockResizeBuffer = true;
+ if (mAttachInfo.mHardwareRenderer != null) {
+ // Fence so that any pending invokeFunctor() messages will be processed
+ // before we return from detachFunctor.
+ mAttachInfo.mHardwareRenderer.fence();
+ }
}
public boolean invokeFunctor(long functor, boolean waitForCompletion) {
@@ -710,17 +716,6 @@ public final class ViewRootImpl implements ViewParent,
if (!HardwareRenderer.sRendererDisabled || (HardwareRenderer.sSystemRendererDisabled
&& forceHwAccelerated)) {
- if (!HardwareRenderer.sUseRenderThread) {
- // TODO: Delete
- // Don't enable hardware acceleration when we're not on the main thread
- if (!HardwareRenderer.sSystemRendererDisabled &&
- Looper.getMainLooper() != Looper.myLooper()) {
- Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware "
- + "acceleration outside of the main thread, aborting");
- return;
- }
- }
-
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.destroy(true);
}
@@ -994,13 +989,27 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ /**
+ * Notifies the HardwareRenderer that a new frame will be coming soon.
+ * Currently only {@link ThreadedRenderer} cares about this, and uses
+ * this knowledge to adjust the scheduling of off-thread animations
+ */
+ void notifyRendererOfFramePending() {
+ if (mAttachInfo.mHardwareRenderer != null) {
+ mAttachInfo.mHardwareRenderer.notifyFramePending();
+ }
+ }
+
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
- scheduleConsumeBatchedInput();
+ if (!mUnbufferedInputDispatch) {
+ scheduleConsumeBatchedInput();
+ }
+ notifyRendererOfFramePending();
}
}
@@ -1708,7 +1717,8 @@ public final class ViewRootImpl implements ViewParent,
if (hwInitialized ||
mWidth != mAttachInfo.mHardwareRenderer.getWidth() ||
mHeight != mAttachInfo.mHardwareRenderer.getHeight()) {
- mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight);
+ mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight,
+ mAttachInfo.mRootView.getResources().getDisplayMetrics());
if (!hwInitialized) {
mAttachInfo.mHardwareRenderer.invalidate(mSurface);
mFullRedrawNeeded = true;
@@ -2447,7 +2457,7 @@ public final class ViewRootImpl implements ViewParent,
try {
attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
- mSurface);
+ mSurface, attachInfo.mRootView.getResources().getDisplayMetrics());
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
@@ -2598,7 +2608,7 @@ public final class ViewRootImpl implements ViewParent,
}
final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
- final Rect bounds = mView.mAttachInfo.mTmpInvalRect;
+ final Rect bounds = mAttachInfo.mTmpInvalRect;
if (provider == null) {
host.getBoundsOnScreen(bounds);
} else if (mAccessibilityFocusedVirtualView != null) {
@@ -3145,7 +3155,8 @@ public final class ViewRootImpl implements ViewParent,
mFullRedrawNeeded = true;
try {
mAttachInfo.mHardwareRenderer.initializeIfNeeded(
- mWidth, mHeight, mSurface);
+ mWidth, mHeight, mSurface,
+ mAttachInfo.mRootView.getResources().getDisplayMetrics());
} catch (OutOfResourcesException e) {
Log.e(TAG, "OutOfResourcesException locking surface", e);
try {
@@ -3481,6 +3492,9 @@ public final class ViewRootImpl implements ViewParent,
* Called when an event is being delivered to the next stage.
*/
protected void onDeliverToNext(QueuedInputEvent q) {
+ if (DEBUG_INPUT_STAGES) {
+ Log.v(TAG, "Done with " + getClass().getSimpleName() + ". " + q);
+ }
if (mNext != null) {
mNext.deliver(q);
} else {
@@ -3876,6 +3890,18 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ @Override
+ protected void onDeliverToNext(QueuedInputEvent q) {
+ if (mUnbufferedInputDispatch
+ && q.mEvent instanceof MotionEvent
+ && ((MotionEvent)q.mEvent).isTouchEvent()
+ && isTerminalInputEvent(q.mEvent)) {
+ mUnbufferedInputDispatch = false;
+ scheduleConsumeBatchedInput();
+ }
+ super.onDeliverToNext(q);
+ }
+
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
@@ -3988,10 +4014,15 @@ public final class ViewRootImpl implements ViewParent,
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
- if (mView.dispatchPointerEvent(event)) {
- return FINISH_HANDLED;
+ mAttachInfo.mUnbufferedDispatchRequested = false;
+ boolean handled = mView.dispatchPointerEvent(event);
+ if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
+ mUnbufferedInputDispatch = true;
+ if (mConsumeBatchedInputScheduled) {
+ scheduleConsumeBatchedInputImmediately();
+ }
}
- return FORWARD;
+ return handled ? FINISH_HANDLED : FORWARD;
}
private int processTrackballEvent(QueuedInputEvent q) {
@@ -5256,6 +5287,8 @@ public final class ViewRootImpl implements ViewParent,
writer.print(" mRemoved="); writer.println(mRemoved);
writer.print(innerPrefix); writer.print("mConsumeBatchedInputScheduled=");
writer.println(mConsumeBatchedInputScheduled);
+ writer.print(innerPrefix); writer.print("mConsumeBatchedInputImmediatelyScheduled=");
+ writer.println(mConsumeBatchedInputImmediatelyScheduled);
writer.print(innerPrefix); writer.print("mPendingInputEventCount=");
writer.println(mPendingInputEventCount);
writer.print(innerPrefix); writer.print("mProcessInputEventsScheduled=");
@@ -5307,7 +5340,7 @@ public final class ViewRootImpl implements ViewParent,
RenderNode renderNode = view.mRenderNode;
info[0]++;
if (renderNode != null) {
- info[1] += 0; /* TODO: Memory used by RenderNodes (properties + DisplayLists) */
+ info[1] += renderNode.getDebugSize();
}
if (view instanceof ViewGroup) {
@@ -5515,6 +5548,37 @@ public final class ViewRootImpl implements ViewParent,
return false;
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("QueuedInputEvent{flags=");
+ boolean hasPrevious = false;
+ hasPrevious = flagToString("DELIVER_POST_IME", FLAG_DELIVER_POST_IME, hasPrevious, sb);
+ hasPrevious = flagToString("DEFERRED", FLAG_DEFERRED, hasPrevious, sb);
+ hasPrevious = flagToString("FINISHED", FLAG_FINISHED, hasPrevious, sb);
+ hasPrevious = flagToString("FINISHED_HANDLED", FLAG_FINISHED_HANDLED, hasPrevious, sb);
+ hasPrevious = flagToString("RESYNTHESIZED", FLAG_RESYNTHESIZED, hasPrevious, sb);
+ hasPrevious = flagToString("UNHANDLED", FLAG_UNHANDLED, hasPrevious, sb);
+ if (!hasPrevious) {
+ sb.append("0");
+ }
+ sb.append(", hasNextQueuedEvent=" + (mEvent != null ? "true" : "false"));
+ sb.append(", hasInputEventReceiver=" + (mReceiver != null ? "true" : "false"));
+ sb.append(", mEvent=" + mEvent + "}");
+ return sb.toString();
+ }
+
+ private boolean flagToString(String name, int flag,
+ boolean hasPrevious, StringBuilder sb) {
+ if ((mFlags & flag) != 0) {
+ if (hasPrevious) {
+ sb.append("|");
+ }
+ sb.append(name);
+ return true;
+ }
+ return hasPrevious;
+ }
}
private QueuedInputEvent obtainQueuedInputEvent(InputEvent event,
@@ -5635,6 +5699,7 @@ public final class ViewRootImpl implements ViewParent,
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);
@@ -5674,15 +5739,25 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ void scheduleConsumeBatchedInputImmediately() {
+ if (!mConsumeBatchedInputImmediatelyScheduled) {
+ unscheduleConsumeBatchedInput();
+ mConsumeBatchedInputImmediatelyScheduled = true;
+ mHandler.post(mConsumeBatchedInputImmediatelyRunnable);
+ }
+ }
+
void doConsumeBatchedInput(long frameTimeNanos) {
if (mConsumeBatchedInputScheduled) {
mConsumeBatchedInputScheduled = false;
if (mInputEventReceiver != null) {
- if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)) {
+ if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)
+ && frameTimeNanos != -1) {
// If we consumed a batch here, we want to go ahead and schedule the
// consumption of batched input events on the next frame. Otherwise, we would
// wait until we have more input events pending and might get starved by other
- // things occurring in the process.
+ // things occurring in the process. If the frame time is -1, however, then
+ // we're in a non-batching mode, so there's no need to schedule this.
scheduleConsumeBatchedInput();
}
}
@@ -5710,7 +5785,11 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void onBatchedInputEventPending() {
- scheduleConsumeBatchedInput();
+ if (mUnbufferedInputDispatch) {
+ super.onBatchedInputEventPending();
+ } else {
+ scheduleConsumeBatchedInput();
+ }
}
@Override
@@ -5731,6 +5810,16 @@ public final class ViewRootImpl implements ViewParent,
new ConsumeBatchedInputRunnable();
boolean mConsumeBatchedInputScheduled;
+ final class ConsumeBatchedInputImmediatelyRunnable implements Runnable {
+ @Override
+ public void run() {
+ doConsumeBatchedInput(-1);
+ }
+ }
+ final ConsumeBatchedInputImmediatelyRunnable mConsumeBatchedInputImmediatelyRunnable =
+ new ConsumeBatchedInputImmediatelyRunnable();
+ boolean mConsumeBatchedInputImmediatelyScheduled;
+
final class InvalidateOnAnimationRunnable implements Runnable {
private boolean mPosted;
private final ArrayList<View> mViews = new ArrayList<View>();
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
deleted file mode 100644
index 4730e59..0000000
--- a/core/java/android/view/VolumePanel.java
+++ /dev/null
@@ -1,1076 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import com.android.internal.R;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.DialogInterface.OnDismissListener;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.media.AudioManager;
-import android.media.AudioService;
-import android.media.AudioSystem;
-import android.media.RingtoneManager;
-import android.media.ToneGenerator;
-import android.media.VolumeController;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Vibrator;
-import android.util.Log;
-import android.view.WindowManager.LayoutParams;
-import android.widget.ImageView;
-import android.widget.SeekBar;
-import android.widget.SeekBar.OnSeekBarChangeListener;
-
-import java.util.HashMap;
-
-/**
- * Handle the volume up and down keys.
- *
- * This code really should be moved elsewhere.
- *
- * Seriously, it really really should be moved elsewhere. This is used by
- * android.media.AudioService, which actually runs in the system process, to
- * show the volume dialog when the user changes the volume. What a mess.
- *
- * @hide
- */
-public class VolumePanel extends Handler implements VolumeController {
- private static final String TAG = VolumePanel.class.getSimpleName();
- private static boolean LOGD = false;
-
- /**
- * The delay before playing a sound. This small period exists so the user
- * can press another key (non-volume keys, too) to have it NOT be audible.
- * <p>
- * PhoneWindow will implement this part.
- */
- public static final int PLAY_SOUND_DELAY = 300;
-
- /**
- * The delay before vibrating. This small period exists so if the user is
- * moving to silent mode, it will not emit a short vibrate (it normally
- * would since vibrate is between normal mode and silent mode using hardware
- * keys).
- */
- public static final int VIBRATE_DELAY = 300;
-
- private static final int VIBRATE_DURATION = 300;
- private static final int BEEP_DURATION = 150;
- private static final int MAX_VOLUME = 100;
- private static final int FREE_DELAY = 10000;
- private static final int TIMEOUT_DELAY = 3000;
-
- private static final int MSG_VOLUME_CHANGED = 0;
- private static final int MSG_FREE_RESOURCES = 1;
- private static final int MSG_PLAY_SOUND = 2;
- private static final int MSG_STOP_SOUNDS = 3;
- private static final int MSG_VIBRATE = 4;
- private static final int MSG_TIMEOUT = 5;
- private static final int MSG_RINGER_MODE_CHANGED = 6;
- private static final int MSG_MUTE_CHANGED = 7;
- private static final int MSG_REMOTE_VOLUME_CHANGED = 8;
- private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9;
- private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10;
- private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11;
-
- // Pseudo stream type for master volume
- private static final int STREAM_MASTER = -100;
- // Pseudo stream type for remote volume is defined in AudioService.STREAM_REMOTE_MUSIC
-
- protected Context mContext;
- private AudioManager mAudioManager;
- protected AudioService mAudioService;
- private boolean mRingIsSilent;
- private boolean mShowCombinedVolumes;
- private boolean mVoiceCapable;
-
- // True if we want to play tones on the system stream when the master stream is specified.
- private final boolean mPlayMasterStreamTones;
-
- /** Dialog containing all the sliders */
- private final Dialog mDialog;
- /** Dialog's content view */
- private final View mView;
-
- /** The visible portion of the volume overlay */
- private final ViewGroup mPanel;
- /** Contains the sliders and their touchable icons */
- private final ViewGroup mSliderGroup;
- /** The button that expands the dialog to show all sliders */
- private final View mMoreButton;
- /** Dummy divider icon that needs to vanish with the more button */
- private final View mDivider;
-
- /** Currently active stream that shows up at the top of the list of sliders */
- private int mActiveStreamType = -1;
- /** All the slider controls mapped by stream type */
- private HashMap<Integer,StreamControl> mStreamControls;
-
- private enum StreamResources {
- BluetoothSCOStream(AudioManager.STREAM_BLUETOOTH_SCO,
- R.string.volume_icon_description_bluetooth,
- R.drawable.ic_audio_bt,
- R.drawable.ic_audio_bt,
- false),
- RingerStream(AudioManager.STREAM_RING,
- R.string.volume_icon_description_ringer,
- R.drawable.ic_audio_ring_notif,
- R.drawable.ic_audio_ring_notif_mute,
- false),
- VoiceStream(AudioManager.STREAM_VOICE_CALL,
- R.string.volume_icon_description_incall,
- R.drawable.ic_audio_phone,
- R.drawable.ic_audio_phone,
- false),
- AlarmStream(AudioManager.STREAM_ALARM,
- R.string.volume_alarm,
- R.drawable.ic_audio_alarm,
- R.drawable.ic_audio_alarm_mute,
- false),
- MediaStream(AudioManager.STREAM_MUSIC,
- R.string.volume_icon_description_media,
- R.drawable.ic_audio_vol,
- R.drawable.ic_audio_vol_mute,
- true),
- NotificationStream(AudioManager.STREAM_NOTIFICATION,
- R.string.volume_icon_description_notification,
- R.drawable.ic_audio_notification,
- R.drawable.ic_audio_notification_mute,
- true),
- // for now, use media resources for master volume
- MasterStream(STREAM_MASTER,
- R.string.volume_icon_description_media, //FIXME should have its own description
- R.drawable.ic_audio_vol,
- R.drawable.ic_audio_vol_mute,
- false),
- RemoteStream(AudioService.STREAM_REMOTE_MUSIC,
- R.string.volume_icon_description_media, //FIXME should have its own description
- R.drawable.ic_media_route_on_holo_dark,
- R.drawable.ic_media_route_disabled_holo_dark,
- false);// will be dynamically updated
-
- int streamType;
- int descRes;
- int iconRes;
- int iconMuteRes;
- // RING, VOICE_CALL & BLUETOOTH_SCO are hidden unless explicitly requested
- boolean show;
-
- StreamResources(int streamType, int descRes, int iconRes, int iconMuteRes, boolean show) {
- this.streamType = streamType;
- this.descRes = descRes;
- this.iconRes = iconRes;
- this.iconMuteRes = iconMuteRes;
- this.show = show;
- }
- }
-
- // List of stream types and their order
- private static final StreamResources[] STREAMS = {
- StreamResources.BluetoothSCOStream,
- StreamResources.RingerStream,
- StreamResources.VoiceStream,
- StreamResources.MediaStream,
- StreamResources.NotificationStream,
- StreamResources.AlarmStream,
- StreamResources.MasterStream,
- StreamResources.RemoteStream
- };
-
- /** Object that contains data for each slider */
- private class StreamControl {
- int streamType;
- ViewGroup group;
- ImageView icon;
- SeekBar seekbarView;
- int iconRes;
- int iconMuteRes;
- }
-
- // Synchronize when accessing this
- private ToneGenerator mToneGenerators[];
- private Vibrator mVibrator;
-
- private static AlertDialog sConfirmSafeVolumeDialog;
- private static Object sConfirmSafeVolumeLock = new Object();
-
- private static class WarningDialogReceiver extends BroadcastReceiver
- implements DialogInterface.OnDismissListener {
- private final Context mContext;
- private final Dialog mDialog;
- private final VolumePanel mVolumePanel;
-
- WarningDialogReceiver(Context context, Dialog dialog, VolumePanel volumePanel) {
- mContext = context;
- mDialog = dialog;
- mVolumePanel = volumePanel;
- IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- context.registerReceiver(this, filter);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- mDialog.cancel();
- cleanUp();
- }
-
- @Override
- public void onDismiss(DialogInterface unused) {
- mContext.unregisterReceiver(this);
- cleanUp();
- }
-
- private void cleanUp() {
- synchronized (sConfirmSafeVolumeLock) {
- sConfirmSafeVolumeDialog = null;
- }
- mVolumePanel.forceTimeout();
- mVolumePanel.updateStates();
- }
- }
-
-
- 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
- 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];
- streamRes.show = (streamRes.streamType == STREAM_MASTER);
- }
- }
-
- mDialog = new Dialog(context) {
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE &&
- sConfirmSafeVolumeDialog == null) {
- forceTimeout();
- return true;
- }
- return false;
- }
- };
-
- // Change some window properties
- final Window window = mDialog.getWindow();
- final LayoutParams lp = window.getAttributes();
- lp.token = null;
- // Offset from the top
- lp.y = res.getDimensionPixelOffset(R.dimen.volume_panel_top);
- lp.type = LayoutParams.TYPE_VOLUME_OVERLAY;
- lp.windowAnimations = R.style.Animation_VolumePanel;
- window.setAttributes(lp);
- 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);
-
- 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 (!mShowCombinedVolumes) {
- mMoreButton.setVisibility(View.GONE);
- mDivider.setVisibility(View.GONE);
- } else {
- mMoreButton.setOnClickListener(mClickListener);
- }
-
- final boolean masterVolumeOnly = res.getBoolean(R.bool.config_useMasterVolume);
- final boolean masterVolumeKeySounds = res.getBoolean(R.bool.config_useVolumeKeySounds);
- mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds;
-
- listenToRingerMode();
- }
-
- public void setLayoutDirection(int layoutDirection) {
- mPanel.setLayoutDirection(layoutDirection);
- updateStates();
- }
-
- private void listenToRingerMode() {
- 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();
-
- if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
- removeMessages(MSG_RINGER_MODE_CHANGED);
- sendMessage(obtainMessage(MSG_RINGER_MODE_CHANGED));
- }
- }
- }, filter);
- }
-
- private boolean isMuted(int streamType) {
- if (streamType == STREAM_MASTER) {
- return mAudioManager.isMasterMute();
- } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
- return (mAudioService.getRemoteStreamVolume() <= 0);
- } else {
- return mAudioManager.isStreamMute(streamType);
- }
- }
-
- private int getStreamMaxVolume(int streamType) {
- if (streamType == STREAM_MASTER) {
- return mAudioManager.getMasterMaxVolume();
- } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
- return mAudioService.getRemoteStreamMaxVolume();
- } else {
- return mAudioManager.getStreamMaxVolume(streamType);
- }
- }
-
- private int getStreamVolume(int streamType) {
- if (streamType == STREAM_MASTER) {
- return mAudioManager.getMasterVolume();
- } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
- return mAudioService.getRemoteStreamVolume();
- } else {
- return mAudioManager.getStreamVolume(streamType);
- }
- }
-
- private void setStreamVolume(int streamType, int index, int flags) {
- if (streamType == STREAM_MASTER) {
- mAudioManager.setMasterVolume(index, flags);
- } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
- mAudioService.setRemoteStreamVolume(index);
- } else {
- mAudioManager.setStreamVolume(streamType, index, flags);
- }
- }
-
- private void createSliders() {
- final Resources res = mContext.getResources();
- final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
-
- mStreamControls = new HashMap<Integer, StreamControl>(STREAMS.length);
-
- for (int i = 0; i < STREAMS.length; i++) {
- StreamResources streamRes = STREAMS[i];
-
- final int streamType = streamRes.streamType;
- if (mVoiceCapable && streamRes == StreamResources.NotificationStream) {
- streamRes = StreamResources.RingerStream;
- }
-
- final StreamControl sc = new StreamControl();
- sc.streamType = streamType;
- sc.group = (ViewGroup) inflater.inflate(R.layout.volume_adjust_item, null);
- sc.group.setTag(sc);
- sc.icon = (ImageView) sc.group.findViewById(R.id.stream_icon);
- sc.icon.setTag(sc);
- sc.icon.setContentDescription(res.getString(streamRes.descRes));
- sc.iconRes = streamRes.iconRes;
- sc.iconMuteRes = streamRes.iconMuteRes;
- sc.icon.setImageResource(sc.iconRes);
- sc.seekbarView = (SeekBar) sc.group.findViewById(R.id.seekbar);
- final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
- streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
- sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne);
- sc.seekbarView.setOnSeekBarChangeListener(mSeekListener);
- sc.seekbarView.setTag(sc);
- mStreamControls.put(streamType, sc);
- }
- }
-
- private void reorderSliders(int activeStreamType) {
- mSliderGroup.removeAllViews();
-
- final StreamControl active = mStreamControls.get(activeStreamType);
- if (active == null) {
- Log.e("VolumePanel", "Missing stream type! - " + activeStreamType);
- mActiveStreamType = -1;
- } else {
- mSliderGroup.addView(active.group);
- mActiveStreamType = activeStreamType;
- active.group.setVisibility(View.VISIBLE);
- updateSlider(active);
- }
-
- addOtherVolumes();
- }
-
- private void addOtherVolumes() {
- if (!mShowCombinedVolumes) return;
-
- for (int i = 0; i < STREAMS.length; i++) {
- // Skip the phone specific ones and the active one
- final int streamType = STREAMS[i].streamType;
- if (!STREAMS[i].show || streamType == mActiveStreamType) {
- continue;
- }
- StreamControl sc = mStreamControls.get(streamType);
- mSliderGroup.addView(sc.group);
- updateSlider(sc);
- }
- }
-
- /** Update the mute and progress state of a slider */
- private void updateSlider(StreamControl sc) {
- sc.seekbarView.setProgress(getStreamVolume(sc.streamType));
- final boolean muted = isMuted(sc.streamType);
- // Force reloading the image resource
- sc.icon.setImageDrawable(null);
- sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes);
- if (((sc.streamType == AudioManager.STREAM_RING) ||
- (sc.streamType == AudioManager.STREAM_NOTIFICATION)) &&
- mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
- sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate);
- }
- if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
- // never disable touch interactions for remote playback, the muting is not tied to
- // the state of the phone.
- sc.seekbarView.setEnabled(true);
- } else if ((sc.streamType != mAudioManager.getMasterStreamType() && muted) ||
- (sConfirmSafeVolumeDialog != null)) {
- sc.seekbarView.setEnabled(false);
- } else {
- sc.seekbarView.setEnabled(true);
- }
- }
-
- private void expand() {
- final int count = mSliderGroup.getChildCount();
- for (int i = 0; i < count; i++) {
- mSliderGroup.getChildAt(i).setVisibility(View.VISIBLE);
- }
- mMoreButton.setVisibility(View.INVISIBLE);
- mDivider.setVisibility(View.INVISIBLE);
- }
-
- private void collapse() {
- mMoreButton.setVisibility(View.VISIBLE);
- mDivider.setVisibility(View.VISIBLE);
- final int count = mSliderGroup.getChildCount();
- for (int i = 1; i < count; i++) {
- mSliderGroup.getChildAt(i).setVisibility(View.GONE);
- }
- }
-
- public void updateStates() {
- final int count = mSliderGroup.getChildCount();
- for (int i = 0; i < count; i++) {
- StreamControl sc = (StreamControl) mSliderGroup.getChildAt(i).getTag();
- updateSlider(sc);
- }
- }
-
- public void postVolumeChanged(int streamType, int flags) {
- if (hasMessages(MSG_VOLUME_CHANGED)) return;
- synchronized (this) {
- if (mStreamControls == null) {
- createSliders();
- }
- }
- removeMessages(MSG_FREE_RESOURCES);
- obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
- }
-
- @Override
- public void postRemoteVolumeChanged(int streamType, int flags) {
- if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return;
- synchronized (this) {
- if (mStreamControls == null) {
- createSliders();
- }
- }
- removeMessages(MSG_FREE_RESOURCES);
- 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();
- }
-
- /**
- * Called by AudioService when it has received new remote playback information that
- * would affect the VolumePanel display (mainly volumes). The difference with
- * {@link #postRemoteVolumeChanged(int, int)} is that the handling of the posted message
- * (MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN) will only update the volume slider if it is being
- * displayed.
- * This special code path is due to the fact that remote volume updates arrive to AudioService
- * asynchronously. So after AudioService has sent the volume update (which should be treated
- * 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
- // late and shouldn't warrant the panel to be displayed longer
- obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget();
- }
-
- public void postMasterVolumeChanged(int flags) {
- postVolumeChanged(STREAM_MASTER, flags);
- }
-
- public void postMuteChanged(int streamType, int flags) {
- if (hasMessages(MSG_VOLUME_CHANGED)) return;
- synchronized (this) {
- if (mStreamControls == null) {
- createSliders();
- }
- }
- removeMessages(MSG_FREE_RESOURCES);
- obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget();
- }
-
- public void postMasterMuteChanged(int flags) {
- postMuteChanged(STREAM_MASTER, flags);
- }
-
- public void postDisplaySafeVolumeWarning(int flags) {
- if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return;
- obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget();
- }
-
- /**
- * Override this if you have other work to do when the volume changes (for
- * example, vibrating, playing a sound, etc.). Make sure to call through to
- * the superclass implementation.
- */
- protected void onVolumeChanged(int streamType, int flags) {
-
- if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")");
-
- if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
- synchronized (this) {
- if (mActiveStreamType != streamType) {
- reorderSliders(streamType);
- }
- onShowVolumeChanged(streamType, flags);
- }
- }
-
- if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
- removeMessages(MSG_PLAY_SOUND);
- sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
- }
-
- if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
- removeMessages(MSG_PLAY_SOUND);
- removeMessages(MSG_VIBRATE);
- onStopSounds();
- }
-
- removeMessages(MSG_FREE_RESOURCES);
- sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
- resetTimeout();
- }
-
- protected void onMuteChanged(int streamType, int flags) {
-
- if (LOGD) Log.d(TAG, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")");
-
- StreamControl sc = mStreamControls.get(streamType);
- if (sc != null) {
- sc.icon.setImageResource(isMuted(sc.streamType) ? sc.iconMuteRes : sc.iconRes);
- }
-
- onVolumeChanged(streamType, flags);
- }
-
- protected void onShowVolumeChanged(int streamType, int flags) {
- int index = getStreamVolume(streamType);
-
- mRingIsSilent = false;
-
- if (LOGD) {
- Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType
- + ", flags: " + flags + "), index: " + index);
- }
-
- // get max volume for progress bar
-
- int max = getStreamMaxVolume(streamType);
-
- switch (streamType) {
-
- case AudioManager.STREAM_RING: {
-// setRingerIcon();
- Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
- mContext, RingtoneManager.TYPE_RINGTONE);
- if (ringuri == null) {
- mRingIsSilent = true;
- }
- break;
- }
-
- case AudioManager.STREAM_MUSIC: {
- // Special case for when Bluetooth is active for music
- if ((mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC) &
- (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP |
- AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
- AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
- setMusicIcon(R.drawable.ic_audio_bt, R.drawable.ic_audio_bt_mute);
- } else {
- setMusicIcon(R.drawable.ic_audio_vol, R.drawable.ic_audio_vol_mute);
- }
- break;
- }
-
- case AudioManager.STREAM_VOICE_CALL: {
- /*
- * For in-call voice call volume, there is no inaudible volume.
- * Rescale the UI control so the progress bar doesn't go all
- * the way to zero and don't show the mute icon.
- */
- index++;
- max++;
- break;
- }
-
- case AudioManager.STREAM_ALARM: {
- break;
- }
-
- case AudioManager.STREAM_NOTIFICATION: {
- Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
- mContext, RingtoneManager.TYPE_NOTIFICATION);
- if (ringuri == null) {
- mRingIsSilent = true;
- }
- break;
- }
-
- case AudioManager.STREAM_BLUETOOTH_SCO: {
- /*
- * For in-call voice call volume, there is no inaudible volume.
- * Rescale the UI control so the progress bar doesn't go all
- * the way to zero and don't show the mute icon.
- */
- index++;
- max++;
- break;
- }
-
- case AudioService.STREAM_REMOTE_MUSIC: {
- if (LOGD) { Log.d(TAG, "showing remote volume "+index+" over "+ max); }
- break;
- }
- }
-
- StreamControl sc = mStreamControls.get(streamType);
- if (sc != null) {
- if (sc.seekbarView.getMax() != max) {
- sc.seekbarView.setMax(max);
- }
-
- sc.seekbarView.setProgress(index);
- if (((flags & AudioManager.FLAG_FIXED_VOLUME) != 0) ||
- (streamType != mAudioManager.getMasterStreamType() &&
- streamType != AudioService.STREAM_REMOTE_MUSIC &&
- isMuted(streamType)) ||
- sConfirmSafeVolumeDialog != null) {
- sc.seekbarView.setEnabled(false);
- } else {
- sc.seekbarView.setEnabled(true);
- }
- }
-
- if (!mDialog.isShowing()) {
- 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);
-
- // Showing dialog - use collapsed state
- if (mShowCombinedVolumes) {
- collapse();
- }
- mDialog.show();
- }
-
- // Do a little vibrate if applicable (only when going into vibrate mode)
- if ((streamType != AudioService.STREAM_REMOTE_MUSIC) &&
- ((flags & AudioManager.FLAG_VIBRATE) != 0) &&
- mAudioService.isStreamAffectedByRingerMode(streamType) &&
- mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
- sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
- }
- }
-
- protected void onPlaySound(int streamType, int flags) {
-
- if (hasMessages(MSG_STOP_SOUNDS)) {
- removeMessages(MSG_STOP_SOUNDS);
- // Force stop right now
- onStopSounds();
- }
-
- synchronized (this) {
- ToneGenerator toneGen = getOrCreateToneGenerator(streamType);
- if (toneGen != null) {
- toneGen.startTone(ToneGenerator.TONE_PROP_BEEP);
- sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION);
- }
- }
- }
-
- protected void onStopSounds() {
-
- synchronized (this) {
- int numStreamTypes = AudioSystem.getNumStreamTypes();
- for (int i = numStreamTypes - 1; i >= 0; i--) {
- ToneGenerator toneGen = mToneGenerators[i];
- if (toneGen != null) {
- toneGen.stopTone();
- }
- }
- }
- }
-
- protected void onVibrate() {
-
- // Make sure we ended up in vibrate ringer mode
- if (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) {
- return;
- }
-
- mVibrator.vibrate(VIBRATE_DURATION, AudioManager.STREAM_SYSTEM);
- }
-
- protected void onRemoteVolumeChanged(int streamType, int flags) {
- // streamType is the real stream type being affected, but for the UI sliders, we
- // refer to AudioService.STREAM_REMOTE_MUSIC. We still play the beeps on the real
- // stream type.
- if (LOGD) Log.d(TAG, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")");
-
- if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || mDialog.isShowing()) {
- synchronized (this) {
- if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) {
- reorderSliders(AudioService.STREAM_REMOTE_MUSIC);
- }
- onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags);
- }
- } else {
- if (LOGD) Log.d(TAG, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI");
- }
-
- if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
- removeMessages(MSG_PLAY_SOUND);
- sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
- }
-
- if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
- removeMessages(MSG_PLAY_SOUND);
- removeMessages(MSG_VIBRATE);
- onStopSounds();
- }
-
- removeMessages(MSG_FREE_RESOURCES);
- sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
- resetTimeout();
- }
-
- protected void onRemoteVolumeUpdateIfShown() {
- if (LOGD) Log.d(TAG, "onRemoteVolumeUpdateIfShown()");
- if (mDialog.isShowing()
- && (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC)
- && (mStreamControls != null)) {
- onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0);
- }
- }
-
-
- /**
- * Handler for MSG_SLIDER_VISIBILITY_CHANGED
- * Hide or show a slider
- * @param streamType can be a valid stream type value, or VolumePanel.STREAM_MASTER,
- * or AudioService.STREAM_REMOTE_MUSIC
- * @param visible
- */
- synchronized protected void onSliderVisibilityChanged(int streamType, int visible) {
- if (LOGD) Log.d(TAG, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")");
- boolean isVisible = (visible == 1);
- for (int i = STREAMS.length - 1 ; i >= 0 ; i--) {
- StreamResources streamRes = STREAMS[i];
- if (streamRes.streamType == streamType) {
- streamRes.show = isVisible;
- if (!isVisible && (mActiveStreamType == streamType)) {
- mActiveStreamType = -1;
- }
- break;
- }
- }
- }
-
- protected void onDisplaySafeVolumeWarning(int flags) {
- if ((flags & AudioManager.FLAG_SHOW_UI) != 0 || mDialog.isShowing()) {
- synchronized (sConfirmSafeVolumeLock) {
- if (sConfirmSafeVolumeDialog != null) {
- return;
- }
- sConfirmSafeVolumeDialog = new AlertDialog.Builder(mContext)
- .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();
- }
- })
- .setNegativeButton(com.android.internal.R.string.no, null)
- .setIconAttribute(android.R.attr.alertDialogIcon)
- .create();
- final WarningDialogReceiver warning = new WarningDialogReceiver(mContext,
- sConfirmSafeVolumeDialog, this);
-
- sConfirmSafeVolumeDialog.setOnDismissListener(warning);
- sConfirmSafeVolumeDialog.getWindow().setType(
- WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
- sConfirmSafeVolumeDialog.show();
- }
- updateStates();
- }
- resetTimeout();
- }
-
- /**
- * Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
- */
- private ToneGenerator getOrCreateToneGenerator(int streamType) {
- if (streamType == STREAM_MASTER) {
- // For devices that use the master volume setting only but still want to
- // play a volume-changed tone, direct the master volume pseudostream to
- // the system stream's tone generator.
- if (mPlayMasterStreamTones) {
- streamType = AudioManager.STREAM_SYSTEM;
- } else {
- return null;
- }
- }
- synchronized (this) {
- if (mToneGenerators[streamType] == null) {
- try {
- mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME);
- } catch (RuntimeException e) {
- if (LOGD) {
- Log.d(TAG, "ToneGenerator constructor failed with "
- + "RuntimeException: " + e);
- }
- }
- }
- return mToneGenerators[streamType];
- }
- }
-
-
- /**
- * Switch between icons because Bluetooth music is same as music volume, but with
- * different icons.
- */
- private void setMusicIcon(int resId, int resMuteId) {
- StreamControl sc = mStreamControls.get(AudioManager.STREAM_MUSIC);
- if (sc != null) {
- sc.iconRes = resId;
- sc.iconMuteRes = resMuteId;
- sc.icon.setImageResource(isMuted(sc.streamType) ? sc.iconMuteRes : sc.iconRes);
- }
- }
-
- protected void onFreeResources() {
- synchronized (this) {
- for (int i = mToneGenerators.length - 1; i >= 0; i--) {
- if (mToneGenerators[i] != null) {
- mToneGenerators[i].release();
- }
- mToneGenerators[i] = null;
- }
- }
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
-
- case MSG_VOLUME_CHANGED: {
- onVolumeChanged(msg.arg1, msg.arg2);
- break;
- }
-
- case MSG_MUTE_CHANGED: {
- onMuteChanged(msg.arg1, msg.arg2);
- break;
- }
-
- case MSG_FREE_RESOURCES: {
- onFreeResources();
- break;
- }
-
- case MSG_STOP_SOUNDS: {
- onStopSounds();
- break;
- }
-
- case MSG_PLAY_SOUND: {
- onPlaySound(msg.arg1, msg.arg2);
- break;
- }
-
- case MSG_VIBRATE: {
- onVibrate();
- break;
- }
-
- case MSG_TIMEOUT: {
- if (mDialog.isShowing()) {
- mDialog.dismiss();
- mActiveStreamType = -1;
- }
- synchronized (sConfirmSafeVolumeLock) {
- if (sConfirmSafeVolumeDialog != null) {
- sConfirmSafeVolumeDialog.dismiss();
- }
- }
- break;
- }
- case MSG_RINGER_MODE_CHANGED: {
- if (mDialog.isShowing()) {
- updateStates();
- }
- break;
- }
-
- case MSG_REMOTE_VOLUME_CHANGED: {
- onRemoteVolumeChanged(msg.arg1, msg.arg2);
- break;
- }
-
- case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN:
- onRemoteVolumeUpdateIfShown();
- break;
-
- case MSG_SLIDER_VISIBILITY_CHANGED:
- onSliderVisibilityChanged(msg.arg1, msg.arg2);
- break;
-
- case MSG_DISPLAY_SAFE_VOLUME_WARNING:
- onDisplaySafeVolumeWarning(msg.arg1);
- break;
- }
- }
-
- private void resetTimeout() {
- removeMessages(MSG_TIMEOUT);
- sendMessageDelayed(obtainMessage(MSG_TIMEOUT), TIMEOUT_DELAY);
- }
-
- private void forceTimeout() {
- removeMessages(MSG_TIMEOUT);
- sendMessage(obtainMessage(MSG_TIMEOUT));
- }
-
- 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();
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- }
-
- @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));
- }
- }
- }
- };
-
- private final View.OnClickListener mClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (v == mMoreButton) {
- expand();
- }
- resetTimeout();
- }
- };
-}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 375f5e3..ecc4586 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -642,9 +642,7 @@ public abstract class Window {
final WindowManager.LayoutParams attrs = getAttributes();
attrs.width = width;
attrs.height = height;
- if (mCallback != null) {
- mCallback.onWindowAttributesChanged(attrs);
- }
+ dispatchWindowAttributesChanged(attrs);
}
/**
@@ -661,9 +659,7 @@ public abstract class Window {
{
final WindowManager.LayoutParams attrs = getAttributes();
attrs.gravity = gravity;
- if (mCallback != null) {
- mCallback.onWindowAttributesChanged(attrs);
- }
+ dispatchWindowAttributesChanged(attrs);
}
/**
@@ -675,9 +671,7 @@ public abstract class Window {
public void setType(int type) {
final WindowManager.LayoutParams attrs = getAttributes();
attrs.type = type;
- if (mCallback != null) {
- mCallback.onWindowAttributesChanged(attrs);
- }
+ dispatchWindowAttributesChanged(attrs);
}
/**
@@ -700,9 +694,7 @@ public abstract class Window {
attrs.format = mDefaultWindowFormat;
mHaveWindowFormat = false;
}
- if (mCallback != null) {
- mCallback.onWindowAttributesChanged(attrs);
- }
+ dispatchWindowAttributesChanged(attrs);
}
/**
@@ -715,9 +707,7 @@ public abstract class Window {
public void setWindowAnimations(int resId) {
final WindowManager.LayoutParams attrs = getAttributes();
attrs.windowAnimations = resId;
- if (mCallback != null) {
- mCallback.onWindowAttributesChanged(attrs);
- }
+ dispatchWindowAttributesChanged(attrs);
}
/**
@@ -735,9 +725,7 @@ public abstract class Window {
} else {
mHasSoftInputMode = false;
}
- if (mCallback != null) {
- mCallback.onWindowAttributesChanged(attrs);
- }
+ dispatchWindowAttributesChanged(attrs);
}
/**
@@ -793,14 +781,19 @@ public abstract class Window {
attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY;
}
mForcedWindowFlags |= mask;
- if (mCallback != null) {
- mCallback.onWindowAttributesChanged(attrs);
- }
+ dispatchWindowAttributesChanged(attrs);
}
private void setPrivateFlags(int flags, int mask) {
final WindowManager.LayoutParams attrs = getAttributes();
attrs.privateFlags = (attrs.privateFlags & ~mask) | (flags & mask);
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * {@hide}
+ */
+ protected void dispatchWindowAttributesChanged(WindowManager.LayoutParams attrs) {
if (mCallback != null) {
mCallback.onWindowAttributesChanged(attrs);
}
@@ -818,9 +811,7 @@ public abstract class Window {
final WindowManager.LayoutParams attrs = getAttributes();
attrs.dimAmount = amount;
mHaveDimAmount = true;
- if (mCallback != null) {
- mCallback.onWindowAttributesChanged(attrs);
- }
+ dispatchWindowAttributesChanged(attrs);
}
/**
@@ -835,9 +826,7 @@ public abstract class Window {
*/
public void setAttributes(WindowManager.LayoutParams a) {
mWindowAttributes.copyFrom(a);
- if (mCallback != null) {
- mCallback.onWindowAttributesChanged(mWindowAttributes);
- }
+ dispatchWindowAttributesChanged(mWindowAttributes);
}
/**
@@ -1269,9 +1258,7 @@ public abstract class Window {
if (!mHaveWindowFormat) {
final WindowManager.LayoutParams attrs = getAttributes();
attrs.format = format;
- if (mCallback != null) {
- mCallback.onWindowAttributesChanged(attrs);
- }
+ dispatchWindowAttributesChanged(attrs);
}
}
@@ -1527,4 +1514,44 @@ public abstract class Window {
* until the called Activity's exiting transition completes.
*/
public boolean getAllowExitTransitionOverlap() { return true; }
+
+ /**
+ * @return the color of the status bar.
+ */
+ public abstract int getStatusBarColor();
+
+ /**
+ * Sets the color of the status bar to {@param color}.
+ *
+ * For this to take effect,
+ * the window must be drawing the system bar backgrounds with
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
+ * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS} must not be set.
+ *
+ * If {@param color} is not opaque, consider setting
+ * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
+ * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.
+ */
+ public abstract void setStatusBarColor(int color);
+
+ /**
+ * @return the color of the navigation bar.
+ */
+ public abstract int getNavigationBarColor();
+
+ /**
+ * Sets the color of the navigation bar to {@param color}.
+ *
+ * For this to take effect,
+ * the window must be drawing the system bar backgrounds with
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
+ * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_NAVIGATION} must not be set.
+ *
+ * If {@param color} is not opaque, consider setting
+ * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
+ * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.
+ */
+ public abstract void setNavigationBarColor(int color);
+
+
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 032a82f..4eecc6a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -218,7 +218,8 @@ public interface WindowManager extends ViewManager {
@ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL, to = "TYPE_NAVIGATION_BAR_PANEL"),
@ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY, to = "TYPE_DISPLAY_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY, to = "TYPE_MAGNIFICATION_OVERLAY"),
- @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION, to = "TYPE_PRIVATE_PRESENTATION")
+ @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION, to = "TYPE_PRIVATE_PRESENTATION"),
+ @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION, to = "TYPE_VOICE_INTERACTION"),
})
public int type;
@@ -541,6 +542,12 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;
/**
+ * Window type: Windows in the voice interaction layer.
+ * @hide
+ */
+ public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;
+
+ /**
* End of types of system windows.
*/
public static final int LAST_SYSTEM_WINDOW = 2999;
@@ -915,6 +922,14 @@ public interface WindowManager extends ViewManager {
public static final int FLAG_NEEDS_MENU_KEY = 0x40000000;
/**
+ * Flag indicating that this Window is responsible for drawing the background for the
+ * system bars. If set, the system bars are drawn with a transparent background and the
+ * corresponding areas in this window are filled with the colors specified in
+ * {@link Window#getStatusBarColor()} and {@link Window#getNavigationBarColor()}.
+ */
+ public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;
+
+ /**
* Various behavioral options/flags. Default is none.
*
* @see #FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
@@ -941,6 +956,7 @@ public interface WindowManager extends ViewManager {
* @see #FLAG_SPLIT_TOUCH
* @see #FLAG_HARDWARE_ACCELERATED
* @see #FLAG_LOCAL_FOCUS_MODE
+ * @see #FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
*/
@ViewDebug.ExportedProperty(flagMapping = {
@ViewDebug.FlagToString(mask = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON, equals = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON,
@@ -998,7 +1014,9 @@ public interface WindowManager extends ViewManager {
@ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_STATUS, equals = FLAG_TRANSLUCENT_STATUS,
name = "FLAG_TRANSLUCENT_STATUS"),
@ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_NAVIGATION, equals = FLAG_TRANSLUCENT_NAVIGATION,
- name = "FLAG_TRANSLUCENT_NAVIGATION")
+ name = "FLAG_TRANSLUCENT_NAVIGATION"),
+ @ViewDebug.FlagToString(mask = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, equals = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+ name = "FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS")
})
public int flags;
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 96c0ed2..b4779f4 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -430,7 +430,7 @@ public final class WindowManagerGlobal {
HardwareRenderer renderer =
root.getView().mAttachInfo.mHardwareRenderer;
if (renderer != null) {
- renderer.dumpGfxInfo(pw);
+ renderer.dumpGfxInfo(pw, fd);
}
}
@@ -447,11 +447,6 @@ public final class WindowManagerGlobal {
String name = getWindowName(root);
pw.printf(" %s\n %d views, %.2f kB of display lists",
name, info[0], info[1] / 1024.0f);
- HardwareRenderer renderer =
- root.getView().mAttachInfo.mHardwareRenderer;
- if (renderer != null) {
- pw.printf(", %d frames rendered", renderer.getFrameCount());
- }
pw.printf("\n\n");
viewsCount += info[0];
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 4fde1e4..2b4677c 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -274,6 +274,11 @@ public interface WindowManagerPolicy {
public IApplicationToken getAppToken();
/**
+ * Return true if this window is participating in voice interaction.
+ */
+ public boolean isVoiceInteraction();
+
+ /**
* Return true if, at any point, the application token associated with
* this window has actually displayed any windows. This is most useful
* with the "starting up" window to determine if any windows were
@@ -603,8 +608,15 @@ public interface WindowManagerPolicy {
* Return whether the given window should forcibly hide everything
* behind it. Typically returns true for the keyguard.
*/
- public boolean doesForceHide(WindowState win, WindowManager.LayoutParams attrs);
-
+ public boolean doesForceHide(WindowManager.LayoutParams attrs);
+
+
+ /**
+ * Return whether the given window can become one that passes doesForceHide() test.
+ * Typically returns true for the StatusBar.
+ */
+ public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs);
+
/**
* Determine if a window that is behind one that is force hiding
* (as determined by {@link #doesForceHide}) should actually be hidden.
@@ -613,7 +625,7 @@ public interface WindowManagerPolicy {
* will conflict with what you set.
*/
public boolean canBeForceHidden(WindowState win, WindowManager.LayoutParams attrs);
-
+
/**
* Called when the system would like to show a UI to indicate that an
* application is starting. You can use this to add a
@@ -1184,4 +1196,12 @@ public interface WindowManagerPolicy {
* @return True if the window is a top level one.
*/
public boolean isTopLevelWindow(int windowType);
+
+ /**
+ * Notifies the keyguard to start fading out.
+ *
+ * @param startTime the start time of the animation in uptime milliseconds
+ * @param fadeoutDuration the duration of the exit animation, in milliseconds
+ */
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 9d10930..66cc3f6 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -17,15 +17,19 @@
package android.view.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.Nullable;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.InputType;
+import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.LongArray;
import android.util.Pools.SynchronizedPool;
import android.view.View;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -112,7 +116,7 @@ public class AccessibilityNodeInfo implements Parcelable {
public static final int ACTION_SELECT = 0x00000004;
/**
- * Action that unselects the node.
+ * Action that deselects the node.
*/
public static final int ACTION_CLEAR_SELECTION = 0x00000008;
@@ -307,6 +311,13 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public static final int ACTION_SET_TEXT = 0x00200000;
+ private static final int LAST_LEGACY_STANDARD_ACTION = ACTION_SET_TEXT;
+
+ /**
+ * Mask to see if the value is larger than the largest ACTION_ constant
+ */
+ private static final int ACTION_TYPE_MASK = 0xFF000000;
+
// Action arguments
/**
@@ -548,7 +559,7 @@ public class AccessibilityNodeInfo implements Parcelable {
private String mViewIdResourceName;
private LongArray mChildNodeIds;
- private int mActions;
+ private ArrayList<AccessibilityAction> mActions;
private int mMovementGranularities;
@@ -875,6 +886,17 @@ public class AccessibilityNodeInfo implements Parcelable {
/**
* Gets the actions that can be performed on the node.
+ */
+ public List<AccessibilityAction> getActionList() {
+ if (mActions == null) {
+ return Collections.emptyList();
+ }
+
+ return mActions;
+ }
+
+ /**
+ * Gets the actions that can be performed on the node.
*
* @return The bit mask of with actions.
*
@@ -892,9 +914,61 @@ public class AccessibilityNodeInfo implements Parcelable {
* @see AccessibilityNodeInfo#ACTION_PREVIOUS_HTML_ELEMENT
* @see AccessibilityNodeInfo#ACTION_SCROLL_FORWARD
* @see AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD
+ *
+ * @deprecated Use {@link #getActionList()}.
*/
+ @Deprecated
public int getActions() {
- return mActions;
+ int returnValue = 0;
+
+ if (mActions == null) {
+ return returnValue;
+ }
+
+ final int actionSize = mActions.size();
+ for (int i = 0; i < actionSize; i++) {
+ int actionId = mActions.get(i).getId();
+ if (actionId <= LAST_LEGACY_STANDARD_ACTION) {
+ returnValue |= actionId;
+ }
+ }
+
+ return returnValue;
+ }
+
+ /**
+ * Adds an action that can be performed on the node.
+ * <p>
+ * To add a standard action use the static constants on {@link AccessibilityAction}.
+ * To add a custom action create a new {@link AccessibilityAction} by passing in a
+ * resource id from your application as the action id and an optional label that
+ * describes the action. To override one of the standard actions use as the action
+ * id of a standard action id such as {@link #ACTION_CLICK} and an optional label that
+ * describes the action.
+ * </p>
+ * <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 addAction(AccessibilityAction action) {
+ enforceNotSealed();
+
+ if (action == null) {
+ return;
+ }
+
+ if (mActions == null) {
+ mActions = new ArrayList<AccessibilityAction>();
+ }
+
+ mActions.remove(action);
+ mActions.add(action);
}
/**
@@ -908,10 +982,20 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param action The action.
*
* @throws IllegalStateException If called from an AccessibilityService.
+ * @throws IllegalArgumentException If the argument is not one of the standard actions.
+ *
+ * @deprecated This has been deprecated for {@link #addAction(AccessibilityAction)}
*/
+ @Deprecated
public void addAction(int action) {
enforceNotSealed();
- mActions |= action;
+
+ if ((action & ACTION_TYPE_MASK) != 0) {
+ throw new IllegalArgumentException("Action is not a combination of the standard " +
+ "actions: " + action);
+ }
+
+ addLegacyStandardActions(action);
}
/**
@@ -923,13 +1007,40 @@ public class AccessibilityNodeInfo implements Parcelable {
* This class is made immutable before being delivered to an AccessibilityService.
* </p>
*
- * @param action The action.
+ * @param action The action to be removed.
*
* @throws IllegalStateException If called from an AccessibilityService.
+ * @deprecated Use {@link #removeAction(AccessibilityAction)}
*/
+ @Deprecated
public void removeAction(int action) {
enforceNotSealed();
- mActions &= ~action;
+
+ removeAction(getActionSingleton(action));
+ }
+
+ /**
+ * 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 to be removed.
+ * @return The action removed from the list of actions.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public boolean removeAction(AccessibilityAction action) {
+ enforceNotSealed();
+
+ if (mActions == null || action == null) {
+ return false;
+ }
+
+ return mActions.remove(action);
}
/**
@@ -2307,7 +2418,29 @@ public class AccessibilityNodeInfo implements Parcelable {
parcel.writeInt(mBoundsInScreen.left);
parcel.writeInt(mBoundsInScreen.right);
- parcel.writeInt(mActions);
+ if (mActions != null && !mActions.isEmpty()) {
+ final int actionCount = mActions.size();
+ parcel.writeInt(actionCount);
+
+ int defaultLegacyStandardActions = 0;
+ for (int i = 0; i < actionCount; i++) {
+ AccessibilityAction action = mActions.get(i);
+ if (isDefaultLegacyStandardAction(action)) {
+ defaultLegacyStandardActions |= action.getId();
+ }
+ }
+ parcel.writeInt(defaultLegacyStandardActions);
+
+ for (int i = 0; i < actionCount; i++) {
+ AccessibilityAction action = mActions.get(i);
+ if (!isDefaultLegacyStandardAction(action)) {
+ parcel.writeInt(action.getId());
+ parcel.writeCharSequence(action.getLabel());
+ }
+ }
+ } else {
+ parcel.writeInt(0);
+ }
parcel.writeInt(mMovementGranularities);
@@ -2388,7 +2521,17 @@ public class AccessibilityNodeInfo implements Parcelable {
mText = other.mText;
mContentDescription = other.mContentDescription;
mViewIdResourceName = other.mViewIdResourceName;
- mActions= other.mActions;
+
+ final ArrayList<AccessibilityAction> otherActions = other.mActions;
+ if (otherActions != null && otherActions.size() > 0) {
+ if (mActions == null) {
+ mActions = new ArrayList(otherActions);
+ } else {
+ mActions.clear();
+ mActions.addAll(other.mActions);
+ }
+ }
+
mBooleanProperties = other.mBooleanProperties;
mMovementGranularities = other.mMovementGranularities;
@@ -2452,7 +2595,17 @@ public class AccessibilityNodeInfo implements Parcelable {
mBoundsInScreen.left = parcel.readInt();
mBoundsInScreen.right = parcel.readInt();
- mActions = parcel.readInt();
+ final int actionCount = parcel.readInt();
+ if (actionCount > 0) {
+ final int legacyStandardActions = parcel.readInt();
+ addLegacyStandardActions(legacyStandardActions);
+ final int nonLegacyActionCount = actionCount - Integer.bitCount(legacyStandardActions);
+ for (int i = 0; i < nonLegacyActionCount; i++) {
+ AccessibilityAction action = new AccessibilityAction(
+ parcel.readInt(), parcel.readCharSequence());
+ addAction(action);
+ }
+ }
mMovementGranularities = parcel.readInt();
@@ -2524,7 +2677,9 @@ public class AccessibilityNodeInfo implements Parcelable {
mText = null;
mContentDescription = null;
mViewIdResourceName = null;
- mActions = 0;
+ if (mActions != null) {
+ mActions.clear();
+ }
mTextSelectionStart = UNDEFINED_SELECTION_INDEX;
mTextSelectionEnd = UNDEFINED_SELECTION_INDEX;
mInputType = InputType.TYPE_NULL;
@@ -2546,6 +2701,33 @@ public class AccessibilityNodeInfo implements Parcelable {
}
}
+ private static boolean isDefaultLegacyStandardAction(AccessibilityAction action) {
+ return (action.getId() <= LAST_LEGACY_STANDARD_ACTION
+ && TextUtils.isEmpty(action.getLabel()));
+ }
+
+ private static AccessibilityAction getActionSingleton(int actionId) {
+ final int actions = AccessibilityAction.sStandardActions.size();
+ for (int i = 0; i < actions; i++) {
+ AccessibilityAction currentAction = AccessibilityAction.sStandardActions.valueAt(i);
+ if (actionId == currentAction.getId()) {
+ return currentAction;
+ }
+ }
+
+ return null;
+ }
+
+ private void addLegacyStandardActions(int actionMask) {
+ int remainingIds = actionMask;
+ while (remainingIds > 0) {
+ final int id = 1 << Integer.numberOfTrailingZeros(remainingIds);
+ remainingIds &= ~id;
+ AccessibilityAction action = getActionSingleton(id);
+ addAction(action);
+ }
+ }
+
/**
* Gets the human readable action symbolic name.
*
@@ -2709,20 +2891,425 @@ public class AccessibilityNodeInfo implements Parcelable {
builder.append("; longClickable: ").append(isLongClickable());
builder.append("; enabled: ").append(isEnabled());
builder.append("; password: ").append(isPassword());
- builder.append("; scrollable: " + isScrollable());
-
- builder.append("; [");
- for (int actionBits = mActions; actionBits != 0;) {
- final int action = 1 << Integer.numberOfTrailingZeros(actionBits);
- actionBits &= ~action;
- builder.append(getActionSymbolicName(action));
- if (actionBits != 0) {
- builder.append(", ");
+ builder.append("; scrollable: ").append(isScrollable());
+ builder.append("; actions: ").append(mActions);
+
+ return builder.toString();
+ }
+
+ /**
+ * A class defining an action that can be performed on an {@link AccessibilityNodeInfo}.
+ * Each action has a unique id that is mandatory and optional data.
+ * <p>
+ * There are three categories of actions:
+ * <ul>
+ * <li><strong>Standard actions</strong> - These are actions that are reported and
+ * handled by the standard UI widgets in the platform. For each standard action
+ * there is a static constant defined in this class, e.g. {@link #ACTION_FOCUS}.
+ * </li>
+ * <li><strong>Custom actions action</strong> - These are actions that are reported
+ * and handled by custom widgets. i.e. ones that are not part of the UI toolkit. For
+ * example, an application may define a custom action for clearing the user history.
+ * </li>
+ * <li><strong>Overriden standard actions</strong> - These are actions that override
+ * standard actions to customize them. For example, an app may add a label to the
+ * standard click action to announce that this action clears browsing history.
+ * </ul>
+ * </p>
+ */
+ public static final class AccessibilityAction {
+
+ /**
+ * Action that gives input focus to the node.
+ */
+ public static final AccessibilityAction ACTION_FOCUS =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_FOCUS, null);
+
+ /**
+ * Action that clears input focus of the node.
+ */
+ public static final AccessibilityAction ACTION_CLEAR_FOCUS =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLEAR_FOCUS, null);
+
+ /**
+ * Action that selects the node.
+ */
+ public static final AccessibilityAction ACTION_SELECT =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_SELECT, null);
+
+ /**
+ * Action that deselects the node.
+ */
+ public static final AccessibilityAction ACTION_CLEAR_SELECTION =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLEAR_SELECTION, null);
+
+ /**
+ * Action that clicks on the node info.
+ */
+ public static final AccessibilityAction ACTION_CLICK =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLICK, null);
+
+ /**
+ * Action that long clicks on the node.
+ */
+ public static final AccessibilityAction ACTION_LONG_CLICK =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_LONG_CLICK, null);
+
+ /**
+ * Action that gives accessibility focus to the node.
+ */
+ public static final AccessibilityAction ACTION_ACCESSIBILITY_FOCUS =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+
+ /**
+ * Action that clears accessibility focus of the node.
+ */
+ public static final AccessibilityAction ACTION_CLEAR_ACCESSIBILITY_FOCUS =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
+
+ /**
+ * Action that requests to go to the next entity in this node's text
+ * at a given movement granularity. For example, move to the next character,
+ * word, etc.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT},
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+ * <strong>Example:</strong> Move to the previous character and do not extend selection.
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+ * false);
+ * info.performAction(AccessibilityAction.ACTION_NEXT_AT_MOVEMENT_GRANULARITY.getId(),
+ * arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ *
+ * @see AccessibilityNodeInfo#setMovementGranularities(int)
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ * @see AccessibilityNodeInfo#getMovementGranularities()
+ * AccessibilityNodeInfo.getMovementGranularities()
+ *
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_CHARACTER
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_WORD
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_LINE
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PARAGRAPH
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PAGE
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE
+ */
+ public static final AccessibilityAction ACTION_NEXT_AT_MOVEMENT_GRANULARITY =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, null);
+
+ /**
+ * Action that requests to go to the previous entity in this node's text
+ * at a given movement granularity. For example, move to the next character,
+ * word, etc.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT},
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+ * <strong>Example:</strong> Move to the next character and do not extend selection.
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+ * false);
+ * info.performAction(AccessibilityAction.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY.getId(),
+ * arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ *
+ * @see AccessibilityNodeInfo#setMovementGranularities(int)
+ * AccessibilityNodeInfo.setMovementGranularities(int)
+ * @see AccessibilityNodeInfo#getMovementGranularities()
+ * AccessibilityNodeInfo.getMovementGranularities()
+ *
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_CHARACTER
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_WORD
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_LINE
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PARAGRAPH
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PAGE
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE
+ */
+ public static final AccessibilityAction ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, null);
+
+ /**
+ * Action to move to the next HTML element of a given type. For example, move
+ * to the BUTTON, INPUT, TABLE, etc.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_HTML_ELEMENT_STRING
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+ * info.performAction(AccessibilityAction.ACTION_NEXT_HTML_ELEMENT.getId(), arguments);
+ * </code></pre></p>
+ * </p>
+ */
+ public static final AccessibilityAction ACTION_NEXT_HTML_ELEMENT =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null);
+
+ /**
+ * Action to move to the previous HTML element of a given type. For example, move
+ * to the BUTTON, INPUT, TABLE, etc.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_HTML_ELEMENT_STRING
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+ * info.performAction(AccessibilityAction.ACTION_PREVIOUS_HTML_ELEMENT.getId(), arguments);
+ * </code></pre></p>
+ * </p>
+ */
+ public static final AccessibilityAction ACTION_PREVIOUS_HTML_ELEMENT =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT, null);
+
+ /**
+ * Action to scroll the node content forward.
+ */
+ public static final AccessibilityAction ACTION_SCROLL_FORWARD =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, null);
+
+ /**
+ * Action to scroll the node content backward.
+ */
+ public static final AccessibilityAction ACTION_SCROLL_BACKWARD =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, null);
+
+ /**
+ * Action to copy the current selection to the clipboard.
+ */
+ public static final AccessibilityAction ACTION_COPY =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_COPY, null);
+
+ /**
+ * Action to paste the current clipboard content.
+ */
+ public static final AccessibilityAction ACTION_PASTE =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_PASTE, null);
+
+ /**
+ * Action to cut the current selection and place it to the clipboard.
+ */
+ public static final AccessibilityAction ACTION_CUT =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CUT, null);
+
+ /**
+ * Action to set the selection. Performing this action with no arguments
+ * clears the selection.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_START_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT},
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_END_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1);
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 2);
+ * info.performAction(AccessibilityAction.ACTION_SET_SELECTION.getId(), arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_START_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_END_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT
+ */
+ public static final AccessibilityAction ACTION_SET_SELECTION =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_SET_SELECTION, null);
+
+ /**
+ * Action to expand an expandable node.
+ */
+ public static final AccessibilityAction ACTION_EXPAND =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_EXPAND, null);
+
+ /**
+ * Action to collapse an expandable node.
+ */
+ public static final AccessibilityAction ACTION_COLLAPSE =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_COLLAPSE, null);
+
+ /**
+ * Action to dismiss a dismissable node.
+ */
+ public static final AccessibilityAction ACTION_DISMISS =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_DISMISS, null);
+
+ /**
+ * 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 AccessibilityNodeInfo#ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE
+ * AccessibilityNodeInfo.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(AccessibilityAction.ACTION_SET_TEXT.getId(), arguments);
+ * </code></pre></p>
+ */
+ public static final AccessibilityAction ACTION_SET_TEXT =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_SET_TEXT, null);
+
+ private static final ArraySet<AccessibilityAction> sStandardActions = new ArraySet<AccessibilityAction>();
+ static {
+ sStandardActions.add(ACTION_FOCUS);
+ sStandardActions.add(ACTION_CLEAR_FOCUS);
+ sStandardActions.add(ACTION_SELECT);
+ sStandardActions.add(ACTION_CLEAR_SELECTION);
+ sStandardActions.add(ACTION_CLICK);
+ sStandardActions.add(ACTION_LONG_CLICK);
+ sStandardActions.add(ACTION_ACCESSIBILITY_FOCUS);
+ sStandardActions.add(ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+ sStandardActions.add(ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
+ sStandardActions.add(ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
+ sStandardActions.add(ACTION_NEXT_HTML_ELEMENT);
+ sStandardActions.add(ACTION_PREVIOUS_HTML_ELEMENT);
+ sStandardActions.add(ACTION_SCROLL_FORWARD);
+ sStandardActions.add(ACTION_SCROLL_BACKWARD);
+ sStandardActions.add(ACTION_COPY);
+ sStandardActions.add(ACTION_PASTE);
+ sStandardActions.add(ACTION_CUT);
+ sStandardActions.add(ACTION_SET_SELECTION);
+ sStandardActions.add(ACTION_EXPAND);
+ sStandardActions.add(ACTION_COLLAPSE);
+ sStandardActions.add(ACTION_DISMISS);
+ sStandardActions.add(ACTION_SET_TEXT);
+ }
+
+ private final int mActionId;
+ private final CharSequence mLabel;
+
+ /**
+ * Creates a new AccessibilityAction. For adding a standard action without a specific label,
+ * use the static constants.
+ *
+ * You can also override the description for one the standard actions. Below is an example
+ * how to override the standard click action by adding a custom label:
+ * <pre>
+ * AccessibilityAction action = new AccessibilityAction(
+ * AccessibilityAction.ACTION_ACTION_CLICK, getLocalizedLabel());
+ * node.addAction(action);
+ * </pre>
+ *
+ * @param actionId The id for this action. This should either be one of the
+ * standard actions or a specific action for your app. In that case it is
+ * required to use a resource identifier.
+ * @param label The label for the new AccessibilityAction.
+ */
+ public AccessibilityAction(int actionId, @Nullable CharSequence label) {
+ if ((actionId & ACTION_TYPE_MASK) == 0 && Integer.bitCount(actionId) != 1) {
+ throw new IllegalArgumentException("Invalid standard action id");
}
+
+ mActionId = actionId;
+ mLabel = label;
}
- builder.append("]");
- return builder.toString();
+ /**
+ * Gets the id for this action.
+ *
+ * @return The action id.
+ */
+ public int getId() {
+ return mActionId;
+ }
+
+ /**
+ * Gets the label for this action. Its purpose is to describe the
+ * action to user.
+ *
+ * @return The label.
+ */
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ @Override
+ public int hashCode() {
+ return mActionId;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) {
+ return false;
+ }
+
+ if (other == this) {
+ return true;
+ }
+
+ if (getClass() != other.getClass()) {
+ return false;
+ }
+
+ return mActionId == ((AccessibilityAction)other).mActionId;
+ }
+
+ @Override
+ public String toString() {
+ return "AccessibilityAction: " + getActionSymbolicName(mActionId) + " - " + mLabel;
+ }
}
/**
diff --git a/core/java/android/view/animation/AccelerateDecelerateInterpolator.java b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
index 158c56e..ed6949a 100644
--- a/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
+++ b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
@@ -19,12 +19,17 @@ package android.view.animation;
import android.content.Context;
import android.util.AttributeSet;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
/**
* An interpolator where the rate of change starts and ends slowly but
* accelerates through the middle.
*
*/
-public class AccelerateDecelerateInterpolator implements Interpolator {
+@HasNativeInterpolator
+public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
public AccelerateDecelerateInterpolator() {
}
@@ -35,4 +40,10 @@ public class AccelerateDecelerateInterpolator implements Interpolator {
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
+ }
}
diff --git a/core/java/android/view/animation/AccelerateInterpolator.java b/core/java/android/view/animation/AccelerateInterpolator.java
index dcab743..c08f348 100644
--- a/core/java/android/view/animation/AccelerateInterpolator.java
+++ b/core/java/android/view/animation/AccelerateInterpolator.java
@@ -20,12 +20,17 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
/**
* An interpolator where the rate of change starts out slowly and
* and then accelerates.
*
*/
-public class AccelerateInterpolator implements Interpolator {
+@HasNativeInterpolator
+public class AccelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
private final float mFactor;
private final double mDoubleFactor;
@@ -64,4 +69,10 @@ public class AccelerateInterpolator implements Interpolator {
return (float)Math.pow(input, mDoubleFactor);
}
}
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createAccelerateInterpolator(mFactor);
+ }
}
diff --git a/core/java/android/view/animation/AnticipateInterpolator.java b/core/java/android/view/animation/AnticipateInterpolator.java
index a6f110e..83a8007 100644
--- a/core/java/android/view/animation/AnticipateInterpolator.java
+++ b/core/java/android/view/animation/AnticipateInterpolator.java
@@ -20,10 +20,15 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
/**
* An interpolator where the change starts backward then flings forward.
*/
-public class AnticipateInterpolator implements Interpolator {
+@HasNativeInterpolator
+public class AnticipateInterpolator implements Interpolator, NativeInterpolatorFactory {
private final float mTension;
public AnticipateInterpolator() {
@@ -53,4 +58,10 @@ public class AnticipateInterpolator implements Interpolator {
// a(t) = t * t * ((tension + 1) * t - tension)
return t * t * ((mTension + 1) * t - mTension);
}
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createAnticipateInterpolator(mTension);
+ }
}
diff --git a/core/java/android/view/animation/AnticipateOvershootInterpolator.java b/core/java/android/view/animation/AnticipateOvershootInterpolator.java
index 3dc9722..1a8adfd 100644
--- a/core/java/android/view/animation/AnticipateOvershootInterpolator.java
+++ b/core/java/android/view/animation/AnticipateOvershootInterpolator.java
@@ -19,6 +19,11 @@ package android.view.animation;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
import static com.android.internal.R.styleable.AnticipateOvershootInterpolator_extraTension;
import static com.android.internal.R.styleable.AnticipateOvershootInterpolator_tension;
import static com.android.internal.R.styleable.AnticipateOvershootInterpolator;
@@ -27,7 +32,8 @@ import static com.android.internal.R.styleable.AnticipateOvershootInterpolator;
* An interpolator where the change starts backward then flings forward and overshoots
* the target value and finally goes back to the final value.
*/
-public class AnticipateOvershootInterpolator implements Interpolator {
+@HasNativeInterpolator
+public class AnticipateOvershootInterpolator implements Interpolator, NativeInterpolatorFactory {
private final float mTension;
public AnticipateOvershootInterpolator() {
@@ -80,4 +86,10 @@ public class AnticipateOvershootInterpolator implements Interpolator {
if (t < 0.5f) return 0.5f * a(t * 2.0f, mTension);
else return 0.5f * (o(t * 2.0f - 2.0f, mTension) + 2.0f);
}
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createAnticipateOvershootInterpolator(mTension);
+ }
}
diff --git a/core/java/android/view/animation/BounceInterpolator.java b/core/java/android/view/animation/BounceInterpolator.java
index ecf99a7..9d8ca90 100644
--- a/core/java/android/view/animation/BounceInterpolator.java
+++ b/core/java/android/view/animation/BounceInterpolator.java
@@ -19,10 +19,15 @@ package android.view.animation;
import android.content.Context;
import android.util.AttributeSet;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
/**
* An interpolator where the change bounces at the end.
*/
-public class BounceInterpolator implements Interpolator {
+@HasNativeInterpolator
+public class BounceInterpolator implements Interpolator, NativeInterpolatorFactory {
public BounceInterpolator() {
}
@@ -47,4 +52,10 @@ public class BounceInterpolator implements Interpolator {
else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f;
else return bounce(t - 1.0435f) + 0.95f;
}
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createBounceInterpolator();
+ }
} \ No newline at end of file
diff --git a/core/java/android/view/animation/CycleInterpolator.java b/core/java/android/view/animation/CycleInterpolator.java
index d355c23..d1ebf05 100644
--- a/core/java/android/view/animation/CycleInterpolator.java
+++ b/core/java/android/view/animation/CycleInterpolator.java
@@ -20,12 +20,17 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
/**
* Repeats the animation for a specified number of cycles. The
* rate of change follows a sinusoidal pattern.
*
*/
-public class CycleInterpolator implements Interpolator {
+@HasNativeInterpolator
+public class CycleInterpolator implements Interpolator, NativeInterpolatorFactory {
public CycleInterpolator(float cycles) {
mCycles = cycles;
}
@@ -44,4 +49,10 @@ public class CycleInterpolator implements Interpolator {
}
private float mCycles;
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createCycleInterpolator(mCycles);
+ }
}
diff --git a/core/java/android/view/animation/DecelerateInterpolator.java b/core/java/android/view/animation/DecelerateInterpolator.java
index 20e079b..0789a0e 100644
--- a/core/java/android/view/animation/DecelerateInterpolator.java
+++ b/core/java/android/view/animation/DecelerateInterpolator.java
@@ -20,12 +20,17 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
/**
* An interpolator where the rate of change starts out quickly and
* and then decelerates.
*
*/
-public class DecelerateInterpolator implements Interpolator {
+@HasNativeInterpolator
+public class DecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
public DecelerateInterpolator() {
}
@@ -60,4 +65,10 @@ public class DecelerateInterpolator implements Interpolator {
}
private float mFactor = 1.0f;
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createDecelerateInterpolator(mFactor);
+ }
}
diff --git a/core/java/android/view/animation/LinearInterpolator.java b/core/java/android/view/animation/LinearInterpolator.java
index 96a039f..552c611 100644
--- a/core/java/android/view/animation/LinearInterpolator.java
+++ b/core/java/android/view/animation/LinearInterpolator.java
@@ -19,11 +19,16 @@ package android.view.animation;
import android.content.Context;
import android.util.AttributeSet;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
/**
* An interpolator where the rate of change is constant
*
*/
-public class LinearInterpolator implements Interpolator {
+@HasNativeInterpolator
+public class LinearInterpolator implements Interpolator, NativeInterpolatorFactory {
public LinearInterpolator() {
}
@@ -34,4 +39,10 @@ public class LinearInterpolator implements Interpolator {
public float getInterpolation(float input) {
return input;
}
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createLinearInterpolator();
+ }
}
diff --git a/core/java/android/view/animation/OvershootInterpolator.java b/core/java/android/view/animation/OvershootInterpolator.java
index 494f8ab..a2466f1 100644
--- a/core/java/android/view/animation/OvershootInterpolator.java
+++ b/core/java/android/view/animation/OvershootInterpolator.java
@@ -20,11 +20,16 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
/**
* An interpolator where the change flings forward and overshoots the last value
* then comes back.
*/
-public class OvershootInterpolator implements Interpolator {
+@HasNativeInterpolator
+public class OvershootInterpolator implements Interpolator, NativeInterpolatorFactory {
private final float mTension;
public OvershootInterpolator() {
@@ -56,4 +61,10 @@ public class OvershootInterpolator implements Interpolator {
t -= 1.0f;
return t * t * ((mTension + 1) * t + mTension) + 1.0f;
}
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createOvershootInterpolator(mTension);
+ }
}
diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java
index 92455df..fad6747 100644
--- a/core/java/android/view/inputmethod/CursorAnchorInfo.java
+++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java
@@ -35,8 +35,12 @@ import java.util.Objects;
public final class CursorAnchorInfo implements Parcelable {
private final int mSelectionStart;
private final int mSelectionEnd;
- private final int mCandidatesStart;
- private final int mCandidatesEnd;
+
+ private final int mComposingTextStart;
+ /**
+ * The text, tracked as a composing region.
+ */
+ private final String mComposingText;
/**
* Horizontal position of the insertion marker, in the local coordinates that will be
@@ -83,8 +87,8 @@ public final class CursorAnchorInfo implements Parcelable {
public CursorAnchorInfo(final Parcel source) {
mSelectionStart = source.readInt();
mSelectionEnd = source.readInt();
- mCandidatesStart = source.readInt();
- mCandidatesEnd = source.readInt();
+ mComposingTextStart = source.readInt();
+ mComposingText = source.readString();
mInsertionMarkerHorizontal = source.readFloat();
mInsertionMarkerTop = source.readFloat();
mInsertionMarkerBaseline = source.readFloat();
@@ -104,8 +108,8 @@ public final class CursorAnchorInfo implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mSelectionStart);
dest.writeInt(mSelectionEnd);
- dest.writeInt(mCandidatesStart);
- dest.writeInt(mCandidatesEnd);
+ dest.writeInt(mComposingTextStart);
+ dest.writeString(mComposingText);
dest.writeFloat(mInsertionMarkerHorizontal);
dest.writeFloat(mInsertionMarkerTop);
dest.writeFloat(mInsertionMarkerBaseline);
@@ -119,14 +123,17 @@ public final class CursorAnchorInfo implements Parcelable {
@Override
public int hashCode(){
// TODO: Improve the hash function.
- final float floatHash = mSelectionStart + mSelectionEnd + mCandidatesStart + mCandidatesEnd
- + mInsertionMarkerHorizontal + mInsertionMarkerTop + mInsertionMarkerBaseline
- + mInsertionMarkerBottom;
+ final float floatHash = mInsertionMarkerHorizontal + mInsertionMarkerTop
+ + mInsertionMarkerBaseline + mInsertionMarkerBottom;
int hash = floatHash > 0 ? (int) floatHash : (int)(-floatHash);
- if (mCharacterRects != null) {
- hash += mCharacterRects.hashCode();
- }
- hash += mMatrix.hashCode();
+ hash *= 31;
+ hash += mSelectionStart + mSelectionEnd + mComposingTextStart;
+ hash *= 31;
+ hash += Objects.hashCode(mComposingText);
+ hash *= 31;
+ hash += Objects.hashCode(mCharacterRects);
+ hash *= 31;
+ hash += Objects.hashCode(mMatrix);
return hash;
}
@@ -147,8 +154,10 @@ public final class CursorAnchorInfo implements Parcelable {
}
if (mSelectionStart != that.mSelectionStart
|| mSelectionEnd != that.mSelectionEnd
- || mCandidatesStart != that.mCandidatesStart
- || mCandidatesEnd != that.mCandidatesEnd) {
+ || mComposingTextStart != that.mComposingTextStart) {
+ return false;
+ }
+ if (!Objects.equals(mComposingTextStart, that.mComposingTextStart)) {
return false;
}
if (!Objects.equals(mCharacterRects, that.mCharacterRects)) {
@@ -163,13 +172,14 @@ public final class CursorAnchorInfo implements Parcelable {
@Override
public String toString() {
return "SelectionInfo{mSelection=" + mSelectionStart + "," + mSelectionEnd
- + " mCandiadtes=" + mCandidatesStart + "," + mCandidatesEnd
+ + " mComposingTextStart=" + mComposingTextStart
+ + " mComposingText=" + Objects.toString(mComposingText)
+ " mInsertionMarkerHorizontal=" + mInsertionMarkerHorizontal
+ " mInsertionMarkerTop=" + mInsertionMarkerTop
+ " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline
+ " mInsertionMarkerBottom=" + mInsertionMarkerBottom
- + " mCharacterRects=" + (mCharacterRects != null ? mCharacterRects : "null")
- + " mMatrix=" + mMatrix
+ + " mCharacterRects=" + Objects.toString(mCharacterRects)
+ + " mMatrix=" + Objects.toString(mMatrix)
+ "}";
}
@@ -190,16 +200,23 @@ public final class CursorAnchorInfo implements Parcelable {
private int mSelectionEnd = -1;
/**
- * Sets the text range of the composition string. Calling this can be skipped if there is
- * no composition.
+ * Sets the text range of the composing text. Calling this can be skipped if there is
+ * no composing text.
+ * @param index index where the composing text starts.
+ * @param composingText the entire composing text.
*/
- public CursorAnchorInfoBuilder setCandidateRange(final int start, final int end) {
- mCandidateStart = start;
- mCandidateEnd = end;
+ public CursorAnchorInfoBuilder setComposingText(final int index,
+ final CharSequence composingText) {
+ mComposingTextStart = index;
+ if (composingText == null) {
+ mComposingText = null;
+ } else {
+ mComposingText = composingText.toString();
+ }
return this;
}
- private int mCandidateStart = -1;
- private int mCandidateEnd = -1;
+ private int mComposingTextStart = -1;
+ private String mComposingText = null;
/**
* Sets the location of the text insertion point (zero width cursor) as a rectangle in
@@ -273,14 +290,10 @@ public final class CursorAnchorInfo implements Parcelable {
* is interpreted as an identity matrix.
*/
public CursorAnchorInfoBuilder setMatrix(final Matrix matrix) {
- if (matrix != null) {
- mMatrix = matrix;
- } else {
- mMatrix = Matrix.IDENTITY_MATRIX;
- }
+ mMatrix.set(matrix != null ? matrix : Matrix.IDENTITY_MATRIX);
return this;
}
- private Matrix mMatrix = Matrix.IDENTITY_MATRIX;
+ private final Matrix mMatrix = new Matrix(Matrix.IDENTITY_MATRIX);
/**
* @return {@link CursorAnchorInfo} using parameters in this
@@ -297,13 +310,13 @@ public final class CursorAnchorInfo implements Parcelable {
public void reset() {
mSelectionStart = -1;
mSelectionEnd = -1;
- mCandidateStart = -1;
- mCandidateEnd = -1;
+ mComposingTextStart = -1;
+ mComposingText = null;
mInsertionMarkerHorizontal = Float.NaN;
mInsertionMarkerTop = Float.NaN;
mInsertionMarkerBaseline = Float.NaN;
mInsertionMarkerBottom = Float.NaN;
- mMatrix = Matrix.IDENTITY_MATRIX;
+ mMatrix.set(Matrix.IDENTITY_MATRIX);
if (mCharacterRectBuilder != null) {
mCharacterRectBuilder.reset();
}
@@ -313,15 +326,15 @@ public final class CursorAnchorInfo implements Parcelable {
private CursorAnchorInfo(final CursorAnchorInfoBuilder builder) {
mSelectionStart = builder.mSelectionStart;
mSelectionEnd = builder.mSelectionEnd;
- mCandidatesStart = builder.mCandidateStart;
- mCandidatesEnd = builder.mCandidateEnd;
+ mComposingTextStart = builder.mComposingTextStart;
+ mComposingText = builder.mComposingText;
mInsertionMarkerHorizontal = builder.mInsertionMarkerHorizontal;
mInsertionMarkerTop = builder.mInsertionMarkerTop;
mInsertionMarkerBaseline = builder.mInsertionMarkerBaseline;
mInsertionMarkerBottom = builder.mInsertionMarkerBottom;
mCharacterRects = builder.mCharacterRectBuilder != null ?
builder.mCharacterRectBuilder.build() : null;
- mMatrix = builder.mMatrix;
+ mMatrix = new Matrix(builder.mMatrix);
}
/**
@@ -341,19 +354,19 @@ public final class CursorAnchorInfo implements Parcelable {
}
/**
- * Returns the index where the composition starts.
- * @return -1 if there is no composition.
+ * Returns the index where the composing text starts.
+ * @return -1 if there is no composing text.
*/
- public int getCandidatesStart() {
- return mCandidatesStart;
+ public int getComposingTextStart() {
+ return mComposingTextStart;
}
/**
- * Returns the index where the composition ends.
- * @return -1 if there is no composition.
+ * Returns the entire composing text.
+ * @return null if there is no composition.
*/
- public int getCandidatesEnd() {
- return mCandidatesEnd;
+ public String getComposingText() {
+ return mComposingText;
}
/**
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index e1c6f52..f874eb7 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -49,7 +49,6 @@ import android.view.InputEventSender;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewRootImpl;
-import android.view.inputmethod.CursorAnchorInfo.CursorAnchorInfoBuilder;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -57,6 +56,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -318,11 +318,16 @@ public final class InputMethodManager {
int mCursorSelEnd;
int mCursorCandStart;
int mCursorCandEnd;
+
+ /**
+ * The instance that has previously been sent to the input method.
+ */
+ private CursorAnchorInfo mCursorAnchorInfo = null;
+
/**
* The buffer to retrieve the view location in screen coordinates in {@link #updateCursor}.
*/
private final int[] mViewTopLeft = new int[2];
- private final CursorAnchorInfoBuilder mCursorAnchorInfoBuilder = new CursorAnchorInfoBuilder();
// -----------------------------------------------------------
@@ -492,6 +497,9 @@ public final class InputMethodManager {
case SET_CURSOR_ANCHOR_MONITOR_MODE: {
synchronized (mH) {
mCursorAnchorMonitorMode = msg.arg1;
+ // Clear the cache.
+ mCursorRect.setEmpty();
+ mCursorAnchorInfo = null;
}
return;
}
@@ -1176,6 +1184,7 @@ public final class InputMethodManager {
mCursorCandStart = -1;
mCursorCandEnd = -1;
mCursorRect.setEmpty();
+ mCursorAnchorInfo = null;
servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this);
} else {
servedContext = null;
@@ -1538,10 +1547,9 @@ public final class InputMethodManager {
|| mCurrentTextBoxAttribute == null || mCurMethod == null) {
return;
}
+ if (DEBUG) Log.d(TAG, "updateCursor");
mTmpCursorRect.set(left, top, right, bottom);
- if (!mCursorRect.equals(mTmpCursorRect)) {
- if (DEBUG) Log.d(TAG, "updateCursor");
-
+ if (!Objects.equals(mCursorRect, mTmpCursorRect)) {
try {
if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod);
mCursorRect.set(mTmpCursorRect);
@@ -1572,10 +1580,14 @@ public final class InputMethodManager {
|| mCurrentTextBoxAttribute == null || mCurMethod == null) {
return;
}
- if (DEBUG) Log.d(TAG, "updateCursorAnchorInfo");
-
+ if (Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) {
+ Log.w(TAG, "Ignoring redundant updateCursorAnchorInfo: info=" + cursorAnchorInfo);
+ return;
+ }
+ if (DEBUG) Log.v(TAG, "updateCursorAnchorInfo: " + cursorAnchorInfo);
try {
mCurMethod.updateCursorAnchorInfo(cursorAnchorInfo);
+ mCursorAnchorInfo = cursorAnchorInfo;
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index 628da3c..84f395a 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -427,8 +427,12 @@ public class SpellCheckerSession {
@Override
public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
- mHandler.sendMessage(
- Message.obtain(mHandler, MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results));
+ synchronized (this) {
+ if (mHandler != null) {
+ mHandler.sendMessage(Message.obtain(mHandler,
+ MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results));
+ }
+ }
}
}
diff --git a/core/java/android/webkit/BrowserDownloadListener.java b/core/java/android/webkit/BrowserDownloadListener.java
deleted file mode 100644
index 724cc62..0000000
--- a/core/java/android/webkit/BrowserDownloadListener.java
+++ /dev/null
@@ -1,57 +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.webkit;
-
-/**
- * An abstract download listener that allows passing extra information as
- * part of onDownloadStart callback.
- * @hide
- */
-public abstract class BrowserDownloadListener implements DownloadListener {
-
- /**
- * Notify the host application that a file should be downloaded
- * @param url The full url to the content that should be downloaded
- * @param userAgent the user agent to be used for the download.
- * @param contentDisposition Content-disposition http header, if
- * present.
- * @param mimetype The mimetype of the content reported by the server
- * @param referer The referer associated with this url
- * @param contentLength The file size reported by the server
- */
- public abstract void onDownloadStart(String url, String userAgent,
- String contentDisposition, String mimetype, String referer,
- long contentLength);
-
-
- /**
- * Notify the host application that a file should be downloaded
- * @param url The full url to the content that should be downloaded
- * @param userAgent the user agent to be used for the download.
- * @param contentDisposition Content-disposition http header, if
- * present.
- * @param mimetype The mimetype of the content reported by the server
- * @param contentLength The file size reported by the server
- */
- @Override
- public void onDownloadStart(String url, String userAgent,
- String contentDisposition, String mimetype, long contentLength) {
-
- onDownloadStart(url, userAgent, contentDisposition, mimetype, null,
- contentLength);
- }
-}
diff --git a/core/java/android/webkit/EventLogTags.logtags b/core/java/android/webkit/EventLogTags.logtags
index b0b5493..a90aebd 100644
--- a/core/java/android/webkit/EventLogTags.logtags
+++ b/core/java/android/webkit/EventLogTags.logtags
@@ -8,3 +8,4 @@ option java_package android.webkit;
# 70103- used by the browser app itself
70150 browser_snap_center
+70151 exp_det_attempt_to_call_object_getclass (app_signature|3)
diff --git a/core/java/android/webkit/PermissionRequest.java b/core/java/android/webkit/PermissionRequest.java
index 3e33498..fa760b7 100644
--- a/core/java/android/webkit/PermissionRequest.java
+++ b/core/java/android/webkit/PermissionRequest.java
@@ -28,6 +28,7 @@ import android.net.Uri;
public interface PermissionRequest {
/**
* Resource belongs to geolocation service.
+ * @hide - see b/14668406
*/
public final static long RESOURCE_GEOLOCATION = 1 << 0;
/**
diff --git a/core/java/android/webkit/WebBackForwardListClient.java b/core/java/android/webkit/WebBackForwardListClient.java
deleted file mode 100644
index 7fe9281..0000000
--- a/core/java/android/webkit/WebBackForwardListClient.java
+++ /dev/null
@@ -1,40 +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.webkit;
-
-/**
- * Interface to receive notifications when items are added to the
- * {@link WebBackForwardList}.
- * {@hide}
- */
-public abstract class WebBackForwardListClient {
-
- /**
- * Notify the client that <var>item</var> has been added to the
- * WebBackForwardList.
- * @param item The newly created WebHistoryItem
- */
- public void onNewHistoryItem(WebHistoryItem item) { }
-
- /**
- * Notify the client that the <var>item</var> at <var>index</var> is now
- * the current history item.
- * @param item A WebHistoryItem
- * @param index The new history index
- */
- public void onIndexChanged(WebHistoryItem item, int index) { }
-}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 7c32c5b..d14c19b 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1460,4 +1460,36 @@ public abstract class WebSettings {
* {@link #MIXED_CONTENT_NEVER_ALLOW} or {@link #MIXED_CONTENT_COMPATIBILITY_MODE}.
*/
public abstract int getMixedContentMode();
+
+ /**
+ * Sets whether to use a video overlay for embedded encrypted video.
+ * In API levels prior to {@link android.os.Build.VERSION_CODES#L}, encrypted video can
+ * only be rendered directly on a secure video surface, so it had been a hard problem to play
+ * encrypted video in HTML. When this flag is on, WebView can play encrypted video (MSE/EME)
+ * by using a video overlay (aka hole-punching) for videos embedded using HTML &lt;video&gt;
+ * tag.<br>
+ * Caution: This setting is intended for use only in a narrow set of circumstances and apps
+ * should only enable it if they require playback of encrypted video content. It will impose
+ * the following limitations on the WebView:
+ * <ul>
+ * <li> Only one video overlay can be played at a time.
+ * <li> Changes made to position or dimensions of a video element may be propagated to the
+ * corresponding video overlay with a noticeable delay.
+ * <li> The video overlay is not visible to web APIs and as such may not interact with
+ * script or styling. For example, CSS styles applied to the &lt;video&gt; tag may be ignored.
+ * </ul>
+ * This is not an exhaustive set of constraints and it may vary with new versions of the
+ * WebView.
+ * @hide
+ */
+ public abstract void setVideoOverlayForEmbeddedEncryptedVideoEnabled(boolean flag);
+
+ /**
+ * Gets whether a video overlay will be used for embedded encrypted video.
+ *
+ * @return true if WebView uses a video overlay for embedded encrypted video.
+ * @see #setVideoOverlayForEmbeddedEncryptedVideoEnabled
+ * @hide
+ */
+ public abstract boolean getVideoOverlayForEmbeddedEncryptedVideoEnabled();
}
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 25bcd44..aaf0a75 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -30,6 +30,9 @@ public final class WebViewFactory {
private static final String CHROMIUM_WEBVIEW_FACTORY =
"com.android.webview.chromium.WebViewChromiumFactoryProvider";
+ private static final String NULL_WEBVIEW_FACTORY =
+ "com.android.webview.nullwebview.NullWebViewFactoryProvider";
+
private static final String LOGTAG = "WebViewFactory";
private static final boolean DEBUG = false;
@@ -50,28 +53,6 @@ public final class WebViewFactory {
private static WebViewFactoryProvider sProviderInstance;
private static final Object sProviderLock = new Object();
- public static boolean isExperimentalWebViewAvailable() {
- // TODO: Remove callers of this method then remove it.
- return false; // Hide the toggle in Developer Settings.
- }
-
- /** @hide */
- public static void setUseExperimentalWebView(boolean enable) {
- // TODO: Remove callers of this method then remove it.
- }
-
- /** @hide */
- public static boolean useExperimentalWebView() {
- // TODO: Remove callers of this method then remove it.
- return true;
- }
-
- /** @hide */
- public static boolean isUseExperimentalWebViewSet() {
- // TODO: Remove callers of this method then remove it.
- return false; // User has not modifed Developer Settings
- }
-
static WebViewFactoryProvider getProvider() {
synchronized (sProviderLock) {
// For now the main purpose of this function (and the factory abstraction) is to keep
@@ -110,6 +91,11 @@ public final class WebViewFactory {
}
private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException {
- return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY);
+ try {
+ return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY);
+ } catch (ClassNotFoundException e) {
+ Log.e(LOGTAG, "Chromium WebView does not exist");
+ return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
+ }
}
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index f4cd5fc..9a46052 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -743,7 +743,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*
* @param view The view whose scroll state is being reported
*
- * @param scrollState The current scroll state. One of
+ * @param scrollState The current scroll state. One of
* {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
*/
public void onScrollStateChanged(AbsListView view, int scrollState);
@@ -2495,29 +2495,31 @@ 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.
+ * Positions the selector in a way that mimics keyboard focus.
*/
void positionSelectorLikeFocus(int position, View sel) {
+ // If we're changing position, update the visibility since the selector
+ // is technically being detached from the previous selection.
+ final Drawable selector = mSelector;
+ final boolean manageState = selector != null && mSelectorPosition != position
+ && position != INVALID_POSITION;
+ if (manageState) {
+ selector.setVisible(false, false);
+ }
+
positionSelector(position, sel);
- final Drawable selector = mSelector;
- if (selector != null && selector.supportsHotspots() && position != INVALID_POSITION) {
+ if (manageState) {
final Rect bounds = mSelectorRect;
final float x = bounds.exactCenterX();
final float y = bounds.exactCenterY();
- selector.setHotspot(R.attr.state_focused, x, y);
+ selector.setVisible(getVisibility() == VISIBLE, false);
+ selector.setHotspot(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;
}
@@ -2526,8 +2528,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (sel instanceof SelectionBoundsAdjuster) {
((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
}
- positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
- selectorRect.bottom);
+
+ // Adjust for selection padding.
+ selectorRect.left -= mSelectionLeftPadding;
+ selectorRect.top -= mSelectionTopPadding;
+ selectorRect.right += mSelectionRightPadding;
+ selectorRect.bottom += mSelectionBottomPadding;
+
+ // Update the selector drawable.
+ final Drawable selector = mSelector;
+ if (selector != null) {
+ selector.setBounds(selectorRect);
+ }
final boolean isChildViewEnabled = mIsChildViewEnabled;
if (sel.isEnabled() != isChildViewEnabled) {
@@ -2538,11 +2550,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
- private void positionSelector(int l, int t, int r, int b) {
- mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
- + mSelectionRightPadding, b + mSelectionBottomPadding);
- }
-
@Override
protected void dispatchDraw(Canvas canvas) {
int saveCount = 0;
@@ -3245,9 +3252,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
((TransitionDrawable) d).resetTransition();
}
}
- if (d.supportsHotspots()) {
- d.setHotspot(R.attr.state_pressed, x, y);
- }
+ d.setHotspot(x, y);
}
if (longClickable) {
@@ -3267,7 +3272,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
- private boolean startScrollIfNeeded(int y, MotionEvent vtev) {
+ private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
// Check if we have moved far enough that it looks more like a
// scroll than a tap
final int deltaY = y - mMotionY;
@@ -3296,27 +3301,31 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
- scrollIfNeeded(y, vtev);
+ scrollIfNeeded(x, y, vtev);
return true;
}
return false;
}
- private void scrollIfNeeded(int y, MotionEvent vtev) {
+ private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
int rawDeltaY = y - mMotionY;
+ int scrollOffsetCorrection = 0;
+ int scrollConsumedCorrection = 0;
+ if (mLastY == Integer.MIN_VALUE) {
+ rawDeltaY -= mMotionCorrection;
+ }
if (dispatchNestedPreScroll(0, rawDeltaY, mScrollConsumed, mScrollOffset)) {
rawDeltaY -= mScrollConsumed[1];
- mMotionCorrection -= mScrollOffset[1];
- if (mLastY != Integer.MIN_VALUE) {
- mLastY -= mScrollOffset[1] + mScrollConsumed[1];
- }
+ scrollOffsetCorrection -= mScrollOffset[1];
+ scrollConsumedCorrection -= mScrollConsumed[1];
if (vtev != null) {
vtev.offsetLocation(0, mScrollOffset[1]);
}
}
- final int deltaY = rawDeltaY - mMotionCorrection;
- int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
+ final int deltaY = rawDeltaY;
+ int incrementalDeltaY =
+ mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
int lastYCorrection = 0;
if (mTouchMode == TOUCH_MODE_SCROLL) {
@@ -3378,46 +3387,51 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
(motionViewRealTop - motionViewPrevTop);
if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
mScrollOffset)) {
- mMotionCorrection -= mScrollOffset[1];
lastYCorrection -= mScrollOffset[1];
if (vtev != null) {
vtev.offsetLocation(0, mScrollOffset[1]);
}
} else {
- overScrollBy(0, overscroll, 0, mScrollY, 0, 0,
- 0, mOverscrollDistance, true);
- if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
- // Don't allow overfling if we're at the edge.
- if (mVelocityTracker != null) {
- mVelocityTracker.clear();
- }
+ final boolean atOverscrollEdge = overScrollBy(0, overscroll,
+ 0, mScrollY, 0, 0, 0, mOverscrollDistance, true);
+
+ if (atOverscrollEdge && mVelocityTracker != null) {
+ // Don't allow overfling if we're at the edge
+ mVelocityTracker.clear();
}
final int overscrollMode = getOverScrollMode();
if (overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
!contentFits())) {
- mDirection = 0; // Reset when entering overscroll.
- mTouchMode = TOUCH_MODE_OVERSCROLL;
- if (deltaY > 0) {
- mEdgeGlowTop.onPull((float) overscroll / getHeight());
+ if (!atOverscrollEdge) {
+ mDirection = 0; // Reset when entering overscroll.
+ mTouchMode = TOUCH_MODE_OVERSCROLL;
+ }
+ if (incrementalDeltaY > 0) {
+ mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
+ (float) x / getWidth());
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
- invalidate(mEdgeGlowTop.getBounds(false));
- } else if (deltaY < 0) {
- mEdgeGlowBottom.onPull((float) overscroll / getHeight());
+ invalidate(0, 0, getWidth(),
+ mEdgeGlowTop.getMaxHeight() + getPaddingTop());
+ } else if (incrementalDeltaY < 0) {
+ mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
+ 1.f - (float) x / getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
- invalidate(mEdgeGlowBottom.getBounds(true));
+ invalidate(0, getHeight() - getPaddingBottom() -
+ mEdgeGlowBottom.getMaxHeight(), getWidth(),
+ getHeight());
}
}
}
}
- mMotionY = y;
+ mMotionY = y + scrollOffsetCorrection;
}
- mLastY = y + lastYCorrection;
+ mLastY = y + lastYCorrection + scrollOffsetCorrection;
}
} else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
if (y != mLastY) {
@@ -3445,17 +3459,22 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
!contentFits())) {
if (rawDeltaY > 0) {
- mEdgeGlowTop.onPull((float) overScrollDistance / getHeight());
+ mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
+ (float) x / getWidth());
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
- invalidate(mEdgeGlowTop.getBounds(false));
+ invalidate(0, 0, getWidth(),
+ mEdgeGlowTop.getMaxHeight() + getPaddingTop());
} else if (rawDeltaY < 0) {
- mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
+ mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
+ 1.f - (float) x / getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
- invalidate(mEdgeGlowBottom.getBounds(true));
+ invalidate(0, getHeight() - getPaddingBottom() -
+ mEdgeGlowBottom.getMaxHeight(), getWidth(),
+ getHeight());
}
}
}
@@ -3703,7 +3722,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
case TOUCH_MODE_DONE_WAITING:
// Check if we have moved far enough that it looks more like a
// scroll than a tap. If so, we'll enter scrolling mode.
- if (startScrollIfNeeded(y, vtev)) {
+ if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
break;
}
// Otherwise, check containment within list bounds. If we're
@@ -3723,7 +3742,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
break;
case TOUCH_MODE_SCROLL:
case TOUCH_MODE_OVERSCROLL:
- scrollIfNeeded(y, vtev);
+ scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
break;
}
}
@@ -3769,9 +3788,7 @@ 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());
- }
+ mSelector.setHotspot(x, ev.getY());
}
if (mTouchModeReset != null) {
removeCallbacks(mTouchModeReset);
@@ -3783,9 +3800,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mTouchMode = TOUCH_MODE_REST;
child.setPressed(false);
setPressed(false);
- if (mSelector != null && mSelector.supportsHotspots()) {
- mSelector.removeHotspot(R.attr.state_pressed);
- }
if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
performClick.run();
}
@@ -4022,8 +4036,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
canvas.translate(leftPadding, edgeY);
mEdgeGlowTop.setSize(width, getHeight());
if (mEdgeGlowTop.draw(canvas)) {
- mEdgeGlowTop.setPosition(leftPadding, edgeY);
- invalidate(mEdgeGlowTop.getBounds(false));
+ invalidate(0, 0, getWidth(),
+ mEdgeGlowTop.getMaxHeight() + getPaddingTop());
}
canvas.restoreToCount(restoreCount);
}
@@ -4040,9 +4054,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
canvas.rotate(180, width, 0);
mEdgeGlowBottom.setSize(width, height);
if (mEdgeGlowBottom.draw(canvas)) {
- // Account for the rotation
- mEdgeGlowBottom.setPosition(edgeX + width, edgeY);
- invalidate(mEdgeGlowBottom.getBounds(true));
+ invalidate(0, getHeight() - getPaddingBottom() -
+ mEdgeGlowBottom.getMaxHeight(), getWidth(),
+ getHeight());
}
canvas.restoreToCount(restoreCount);
}
@@ -4161,7 +4175,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final int y = (int) ev.getY(pointerIndex);
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
- if (startScrollIfNeeded(y, null)) {
+ if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
return true;
}
break;
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 225cd6d..43f623b 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -19,7 +19,9 @@ package android.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.graphics.Insets;
import android.graphics.Rect;
+import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.AttributeSet;
@@ -29,12 +31,13 @@ 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 final Rect mTempRect = new Rect();
+
private Drawable mThumb;
private int mThumbOffset;
-
+ private boolean mSplitTrack;
+
/**
* On touch, this offset plus the scaled value from the position of the
* touch will form the progress value. Usually 0.
@@ -51,10 +54,10 @@ public abstract class AbsSeekBar extends ProgressBar {
* progress.
*/
private int mKeyProgressIncrement = 1;
-
+
private static final int NO_ALPHA = 0xFF;
private float mDisabledAlpha;
-
+
private int mScaledTouchSlop;
private float mTouchDownX;
private boolean mIsDragging;
@@ -76,12 +79,16 @@ public abstract class AbsSeekBar extends ProgressBar {
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
- int thumbOffset = a.getDimensionPixelOffset(
+
+ final Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb);
+ setThumb(thumb);
+
+ // Guess thumb offset if thumb != null, but allow layout to override.
+ final int thumbOffset = a.getDimensionPixelOffset(
com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset());
setThumbOffset(thumbOffset);
+
+ mSplitTrack = a.getBoolean(com.android.internal.R.styleable.SeekBar_splitTrack, false);
a.recycle();
a = context.obtainStyledAttributes(attrs,
@@ -97,7 +104,7 @@ public abstract class AbsSeekBar extends ProgressBar {
* <p>
* If the thumb is a valid drawable (i.e. not null), half its width will be
* used as the new thumb offset (@see #setThumbOffset(int)).
- *
+ *
* @param thumb Drawable representing the thumb
*/
public void setThumb(Drawable thumb) {
@@ -132,7 +139,7 @@ public abstract class AbsSeekBar extends ProgressBar {
mThumb = thumb;
invalidate();
if (needUpdate) {
- updateThumbPos(getWidth(), getHeight());
+ updateThumbAndTrackPos(getWidth(), getHeight());
if (thumb != null && thumb.isStateful()) {
// Note that if the states are different this won't work.
// For now, let's consider that an app bug.
@@ -162,7 +169,7 @@ public abstract class AbsSeekBar extends ProgressBar {
/**
* Sets the thumb offset that allows the thumb to extend out of the range of
* the track.
- *
+ *
* @param thumbOffset The offset amount in pixels.
*/
public void setThumbOffset(int thumbOffset) {
@@ -171,8 +178,27 @@ public abstract class AbsSeekBar extends ProgressBar {
}
/**
+ * Specifies whether the track should be split by the thumb. When true,
+ * the thumb's optical bounds will be clipped out of the track drawable,
+ * then the thumb will be drawn into the resulting gap.
+ *
+ * @param splitTrack Whether the track should be split by the thumb
+ */
+ public void setSplitTrack(boolean splitTrack) {
+ mSplitTrack = splitTrack;
+ invalidate();
+ }
+
+ /**
+ * Returns whether the track should be split by the thumb.
+ */
+ public boolean getSplitTrack() {
+ return mSplitTrack;
+ }
+
+ /**
* Sets the amount of progress changed via the arrow keys.
- *
+ *
* @param increment The amount to increment or decrement when the user
* presses the arrow keys.
*/
@@ -184,14 +210,14 @@ public abstract class AbsSeekBar extends ProgressBar {
* Returns the amount of progress changed via the arrow keys.
* <p>
* By default, this will be a value that is derived from the max progress.
- *
+ *
* @return The amount to increment or decrement when the user presses the
* arrow keys. This will be positive.
*/
public int getKeyProgressIncrement() {
return mKeyProgressIncrement;
}
-
+
@Override
public synchronized void setMax(int max) {
super.setMax(max);
@@ -217,79 +243,95 @@ public abstract class AbsSeekBar extends ProgressBar {
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
-
- Drawable progressDrawable = getProgressDrawable();
+
+ final Drawable progressDrawable = getProgressDrawable();
if (progressDrawable != null) {
progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha));
}
-
- if (mThumb != null && mThumb.isStateful()) {
- int[] state = getDrawableState();
- mThumb.setState(state);
+
+ final Drawable thumb = mThumb;
+ if (thumb != null && thumb.isStateful()) {
+ thumb.setState(getDrawableState());
}
}
-
+
+ @Override
+ public void invalidateDrawable(Drawable dr) {
+ super.invalidateDrawable(dr);
+
+ if (dr == mThumb) {
+ // Handle changes to thumb width and height.
+ requestLayout();
+ }
+ }
+
@Override
void onProgressRefresh(float scale, boolean fromUser) {
super.onProgressRefresh(scale, fromUser);
- Drawable thumb = mThumb;
+
+ final Drawable thumb = mThumb;
if (thumb != null) {
setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE);
- /*
- * Since we draw translated, the drawable's bounds that it signals
- * for invalidation won't be the actual bounds we want invalidated,
- * so just invalidate this whole view.
- */
+
+ // Since we draw translated, the drawable's bounds that it signals
+ // for invalidation won't be the actual bounds we want invalidated,
+ // so just invalidate this whole view.
invalidate();
}
}
-
-
+
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
- updateThumbPos(w, h);
+
+ updateThumbAndTrackPos(w, h);
}
- private void updateThumbPos(int w, int h) {
- Drawable d = getCurrentDrawable();
- Drawable thumb = mThumb;
- int thumbHeight = thumb == null ? 0 : thumb.getIntrinsicHeight();
+ private void updateThumbAndTrackPos(int w, int h) {
+ final Drawable track = getCurrentDrawable();
+ final Drawable thumb = mThumb;
+
// The max height does not incorporate padding, whereas the height
- // parameter does
- int trackHeight = Math.min(mMaxHeight, h - mPaddingTop - mPaddingBottom);
-
- int max = getMax();
- float scale = max > 0 ? (float) getProgress() / (float) max : 0;
-
+ // parameter does.
+ final int trackHeight = Math.min(mMaxHeight, h - mPaddingTop - mPaddingBottom);
+ final int thumbHeight = thumb == null ? 0 : thumb.getIntrinsicHeight();
+
+ // Apply offset to whichever item is taller.
+ final int trackOffset;
+ final int thumbOffset;
if (thumbHeight > trackHeight) {
- if (thumb != null) {
- setThumbPos(w, thumb, scale, 0);
- }
- int gapForCenteringTrack = (thumbHeight - trackHeight) / 2;
- if (d != null) {
- // Canvas will be translated by the padding, so 0,0 is where we start drawing
- d.setBounds(0, gapForCenteringTrack,
- w - mPaddingRight - mPaddingLeft, h - mPaddingBottom - gapForCenteringTrack
- - mPaddingTop);
- }
+ trackOffset = (thumbHeight - trackHeight) / 2;
+ thumbOffset = 0;
} else {
- if (d != null) {
- // Canvas will be translated by the padding, so 0,0 is where we start drawing
- d.setBounds(0, 0, w - mPaddingRight - mPaddingLeft, h - mPaddingBottom
- - mPaddingTop);
- }
- int gap = (trackHeight - thumbHeight) / 2;
- if (thumb != null) {
- setThumbPos(w, thumb, scale, gap);
- }
+ trackOffset = 0;
+ thumbOffset = (trackHeight - thumbHeight) / 2;
+ }
+
+ if (track != null) {
+ track.setBounds(0, trackOffset, w - mPaddingRight - mPaddingLeft,
+ h - mPaddingBottom - trackOffset - mPaddingTop);
+ }
+
+ if (thumb != null) {
+ setThumbPos(w, thumb, getScale(), thumbOffset);
}
}
+ private float getScale() {
+ final int max = getMax();
+ return max > 0 ? getProgress() / (float) max : 0;
+ }
+
/**
- * @param gap If set to {@link Integer#MIN_VALUE}, this will be ignored and
+ * Updates the thumb drawable bounds.
+ *
+ * @param w Width of the view, including padding
+ * @param thumb Drawable used for the thumb
+ * @param scale Current progress between 0 and 1
+ * @param offset Vertical offset for centering. If set to
+ * {@link Integer#MIN_VALUE}, the current offset will be used.
*/
- private void setThumbPos(int w, Drawable thumb, float scale, int gap) {
+ private void setThumbPos(int w, Drawable thumb, float scale, int offset) {
int available = w - mPaddingLeft - mPaddingRight;
final int thumbWidth = thumb.getIntrinsicWidth();
final int thumbHeight = thumb.getIntrinsicHeight();
@@ -301,20 +343,20 @@ public abstract class AbsSeekBar extends ProgressBar {
final int thumbPos = (int) (scale * available + 0.5f);
final int top, bottom;
- if (gap == Integer.MIN_VALUE) {
+ if (offset == Integer.MIN_VALUE) {
final Rect oldBounds = thumb.getBounds();
top = oldBounds.top;
bottom = oldBounds.bottom;
} else {
- top = gap;
- bottom = gap + thumbHeight;
+ top = offset;
+ bottom = offset + thumbHeight;
}
final int left = (isLayoutRtl() && mMirrorForRtl) ? available - thumbPos : thumbPos;
final int right = left + thumbWidth;
final Drawable background = getBackground();
- if (background != null && background.supportsHotspots()) {
+ if (background != null) {
final Rect bounds = mThumb.getBounds();
final int offsetX = mPaddingLeft - mThumbOffset;
final int offsetY = mPaddingTop;
@@ -342,6 +384,33 @@ public abstract class AbsSeekBar extends ProgressBar {
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
+ drawThumb(canvas);
+ }
+
+ @Override
+ void drawTrack(Canvas canvas) {
+ final Drawable thumbDrawable = mThumb;
+ if (thumbDrawable != null && mSplitTrack) {
+ final Insets insets = thumbDrawable.getOpticalInsets();
+ final Rect tempRect = mTempRect;
+ thumbDrawable.copyBounds(tempRect);
+ tempRect.offset(mPaddingLeft - mThumbOffset, mPaddingTop);
+ tempRect.left += insets.left;
+ tempRect.right -= insets.right;
+
+ final int saveCount = canvas.save();
+ canvas.clipRect(tempRect, Op.DIFFERENCE);
+ super.drawTrack(canvas);
+ canvas.restoreToCount(saveCount);
+ } else {
+ super.drawTrack(canvas);
+ }
+ }
+
+ /**
+ * Draw the thumb.
+ */
+ void drawThumb(Canvas canvas) {
if (mThumb != null) {
canvas.save();
// Translate the padding. For the x, we need to allow the thumb to
@@ -366,17 +435,17 @@ public abstract class AbsSeekBar extends ProgressBar {
}
dw += mPaddingLeft + mPaddingRight;
dh += mPaddingTop + mPaddingBottom;
-
+
setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
resolveSizeAndState(dh, heightMeasureSpec, 0));
}
-
+
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!mIsUserSeekable || !isEnabled()) {
return false;
}
-
+
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (isInScrollingContainer()) {
@@ -391,7 +460,7 @@ public abstract class AbsSeekBar extends ProgressBar {
attemptClaimDrag();
}
break;
-
+
case MotionEvent.ACTION_MOVE:
if (mIsDragging) {
trackTouchEvent(event);
@@ -408,7 +477,7 @@ public abstract class AbsSeekBar extends ProgressBar {
}
}
break;
-
+
case MotionEvent.ACTION_UP:
if (mIsDragging) {
trackTouchEvent(event);
@@ -426,7 +495,7 @@ public abstract class AbsSeekBar extends ProgressBar {
// value has not apparently changed)
invalidate();
break;
-
+
case MotionEvent.ACTION_CANCEL:
if (mIsDragging) {
onStopTrackingTouch();
@@ -438,17 +507,10 @@ public abstract class AbsSeekBar extends ProgressBar {
return true;
}
- private void setHotspot(int id, float x, float y) {
+ private void setHotspot(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);
+ if (bg != null) {
+ bg.setHotspot(x, y);
}
}
@@ -480,7 +542,7 @@ public abstract class AbsSeekBar extends ProgressBar {
final int max = getMax();
progress += scale * max;
- setHotspot(R.attr.state_pressed, x, (int) event.getY());
+ setHotspot(x, (int) event.getY());
setProgress((int) progress, true);
}
@@ -493,7 +555,7 @@ public abstract class AbsSeekBar extends ProgressBar {
mParent.requestDisallowInterceptTouchEvent(true);
}
}
-
+
/**
* This is called when the user has started touching this widget.
*/
@@ -506,7 +568,6 @@ public abstract class AbsSeekBar extends ProgressBar {
* canceled.
*/
void onStopTrackingTouch() {
- clearHotspot(R.attr.state_pressed);
mIsDragging = false;
}
@@ -526,7 +587,7 @@ public abstract class AbsSeekBar extends ProgressBar {
setProgress(progress - mKeyProgressIncrement, true);
onKeyChange();
return true;
-
+
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (progress >= getMax()) break;
setProgress(progress + mKeyProgressIncrement, true);
@@ -595,17 +656,13 @@ public abstract class AbsSeekBar extends ProgressBar {
public void onRtlPropertiesChanged(int layoutDirection) {
super.onRtlPropertiesChanged(layoutDirection);
- int max = getMax();
- float scale = max > 0 ? (float) getProgress() / (float) max : 0;
-
- Drawable thumb = mThumb;
+ final Drawable thumb = mThumb;
if (thumb != null) {
- setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE);
- /*
- * Since we draw translated, the drawable's bounds that it signals
- * for invalidation won't be the actual bounds we want invalidated,
- * so just invalidate this whole view.
- */
+ setThumbPos(getWidth(), thumb, getScale(), Integer.MIN_VALUE);
+
+ // Since we draw translated, the drawable's bounds that it signals
+ // for invalidation won't be the actual bounds we want invalidated,
+ // so just invalidate this whole view.
invalidate();
}
}
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index 51759c5..1fddf3e 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -544,6 +544,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
public void setMenuView(ActionMenuView menuView) {
mMenuView = menuView;
+ menuView.initialize(mMenu);
}
private static class SavedState implements Parcelable {
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index 3975edf..acee592 100644
--- a/core/java/android/widget/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -69,6 +69,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
/** @hide */
public void setPresenter(ActionMenuPresenter presenter) {
mPresenter = presenter;
+ mPresenter.setMenuView(this);
}
@Override
@@ -488,7 +489,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mPresenter.dismissPopupMenus();
+ dismissPopupMenus();
}
/** @hide */
@@ -569,15 +570,65 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
mMenu = new MenuBuilder(context);
mMenu.setCallback(new MenuBuilderCallback());
mPresenter = new ActionMenuPresenter(context);
- mPresenter.setMenuView(this);
mPresenter.setCallback(new ActionMenuPresenterCallback());
mMenu.addMenuPresenter(mPresenter);
+ mPresenter.setMenuView(this);
}
return mMenu;
}
/**
+ * Returns the current menu or null if one has not yet been configured.
+ * @hide Internal use only for action bar integration
+ */
+ public MenuBuilder peekMenu() {
+ return mMenu;
+ }
+
+ /**
+ * Show the overflow items from the associated menu.
+ *
+ * @return true if the menu was able to be shown, false otherwise
+ */
+ public boolean showOverflowMenu() {
+ return mPresenter != null && mPresenter.showOverflowMenu();
+ }
+
+ /**
+ * Hide the overflow items from the associated menu.
+ *
+ * @return true if the menu was able to be hidden, false otherwise
+ */
+ public boolean hideOverflowMenu() {
+ return mPresenter != null && mPresenter.hideOverflowMenu();
+ }
+
+ /**
+ * Check whether the overflow menu is currently showing. This may not reflect
+ * a pending show operation in progress.
+ *
+ * @return true if the overflow menu is currently showing
+ */
+ public boolean isOverflowMenuShowing() {
+ return mPresenter != null && mPresenter.isOverflowMenuShowing();
+ }
+
+ /** @hide */
+ public boolean isOverflowMenuShowPending() {
+ return mPresenter != null && mPresenter.isOverflowMenuShowPending();
+ }
+
+ /**
+ * Dismiss any popups associated with this menu view.
+ */
+ public void dismissPopupMenus() {
+ if (mPresenter != null) {
+ mPresenter.dismissPopupMenus();
+ }
+ }
+
+ /**
* @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public.
*/
@Override
@@ -601,6 +652,11 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
return false;
}
+ /** @hide */
+ public void setExpandedActionViewsExclusive(boolean exclusive) {
+ mPresenter.setExpandedActionViewsExclusive(exclusive);
+ }
+
/**
* Interface responsible for receiving menu item click events if the items themselves
* do not have individual item click listeners.
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index 1533510..3ae9508 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -24,6 +24,7 @@ import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
+import android.view.RemotableViewMethod;
import android.view.ViewDebug;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -156,10 +157,36 @@ public class CheckedTextView extends TextView implements Checkable {
mCheckMarkWidth = 0;
}
mCheckMarkDrawable = d;
- // Do padding resolution. This will call internalSetPadding() and do a requestLayout() if needed.
+
+ // Do padding resolution. This will call internalSetPadding() and do a
+ // requestLayout() if needed.
resolvePadding();
}
+ @RemotableViewMethod
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+
+ if (mCheckMarkDrawable != null) {
+ mCheckMarkDrawable.setVisible(visibility == VISIBLE, false);
+ }
+ }
+
+ @Override
+ public void jumpDrawablesToCurrentState() {
+ super.jumpDrawablesToCurrentState();
+
+ if (mCheckMarkDrawable != null) {
+ mCheckMarkDrawable.jumpToCurrentState();
+ }
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return who == mCheckMarkDrawable || super.verifyDrawable(who);
+ }
+
/**
* Gets the checkmark drawable
*
@@ -249,6 +276,11 @@ public class CheckedTextView extends TextView implements Checkable {
}
checkMarkDrawable.setBounds(mScrollX + left, top, mScrollX + right, bottom);
checkMarkDrawable.draw(canvas);
+
+ final Drawable background = getBackground();
+ if (background != null) {
+ background.setHotspotBounds(mScrollX + left, top, mScrollX + right, bottom);
+ }
}
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 9e17cca..6aff4f4 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -285,7 +285,7 @@ public abstract class CompoundButton extends Button implements Checkable {
buttonDrawable.setBounds(left, top, right, bottom);
final Drawable background = getBackground();
- if (background != null && background.supportsHotspots()) {
+ if (background != null) {
background.setHotspotBounds(left, top, right, bottom);
}
}
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 265dbcd..2c1a77c 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -24,6 +24,7 @@ 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;
@@ -814,8 +815,7 @@ public class DatePicker extends FrameLayout {
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());
+ String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd");
char[] order = ICU.getDateFormatOrder(pattern);
final int spinnerCount = order.length;
for (int i = 0; i < spinnerCount; i++) {
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index fa37443..c4a40b4 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -16,13 +16,16 @@
package android.widget;
+import android.content.res.TypedArray;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
-import com.android.internal.R;
+import android.graphics.RectF;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
+import android.util.FloatMath;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
@@ -55,16 +58,11 @@ public class EdgeEffect {
// Time it will take before a pulled glow begins receding in ms
private static final int PULL_TIME = 167;
- // Time it will take in ms for a pulled glow to decay to partial strength before release
- private static final int PULL_DECAY_TIME = 1000;
-
private static final float MAX_ALPHA = 1.f;
- private static final float HELD_EDGE_SCALE_Y = 0.5f;
- private static final float MAX_GLOW_HEIGHT = 4.f;
+ private static final float MAX_GLOW_SCALE = 2.f;
- private static final float PULL_GLOW_BEGIN = 1.f;
- private static final float PULL_EDGE_BEGIN = 0.6f;
+ private static final float PULL_GLOW_BEGIN = 0.f;
// Minimum velocity that will be absorbed
private static final int MIN_VELOCITY = 100;
@@ -73,24 +71,13 @@ public class EdgeEffect {
private static final float EPSILON = 0.001f;
- private final Drawable mEdge;
- private final Drawable mGlow;
- private int mWidth;
- private int mHeight;
- private int mX;
- private int mY;
- private static final int MIN_WIDTH = 300;
- private final int mMinWidth;
-
- private float mEdgeAlpha;
- private float mEdgeScaleY;
+ private static final double ANGLE = Math.PI / 6;
+ private static final float SIN = (float) Math.sin(ANGLE);
+ private static final float COS = (float) Math.cos(ANGLE);
+
private float mGlowAlpha;
private float mGlowScaleY;
- private float mEdgeAlphaStart;
- private float mEdgeAlphaFinish;
- private float mEdgeScaleYStart;
- private float mEdgeScaleYFinish;
private float mGlowAlphaStart;
private float mGlowAlphaFinish;
private float mGlowScaleYStart;
@@ -107,16 +94,11 @@ public class EdgeEffect {
private static final int STATE_RECEDE = 3;
private static final int STATE_PULL_DECAY = 4;
- // How much dragging should effect the height of the edge image.
- // Number determined by user testing.
- private static final int PULL_DISTANCE_EDGE_FACTOR = 7;
-
// How much dragging should effect the height of the glow image.
// Number determined by user testing.
private static final int PULL_DISTANCE_GLOW_FACTOR = 7;
private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 1.1f;
- private static final int VELOCITY_EDGE_FACTOR = 8;
private static final int VELOCITY_GLOW_FACTOR = 12;
private int mState = STATE_IDLE;
@@ -124,30 +106,27 @@ public class EdgeEffect {
private float mPullDistance;
private final Rect mBounds = new Rect();
-
- private final int mEdgeHeight;
- private final int mGlowHeight;
- private final int mGlowWidth;
- private final int mMaxEffectHeight;
+ private final RectF mArcRect = new RectF();
+ private final Paint mPaint = new Paint();
+ private float mRadius;
+ private float mBaseGlowHeight;
+ private float mDisplacement = 0.5f;
+ private float mTargetDisplacement = 0.5f;
/**
* Construct a new EdgeEffect with a theme appropriate for the provided context.
* @param context Context used to provide theming and resource information for the EdgeEffect
*/
public EdgeEffect(Context context) {
- final Resources res = context.getResources();
- mEdge = context.getDrawable(R.drawable.overscroll_edge);
- mGlow = context.getDrawable(R.drawable.overscroll_glow);
-
- mEdgeHeight = mEdge.getIntrinsicHeight();
- mGlowHeight = mGlow.getIntrinsicHeight();
- mGlowWidth = mGlow.getIntrinsicWidth();
-
- mMaxEffectHeight = (int) (Math.min(
- mGlowHeight * MAX_GLOW_HEIGHT * mGlowHeight / mGlowWidth * 0.6f,
- mGlowHeight * MAX_GLOW_HEIGHT) + 0.5f);
-
- mMinWidth = (int) (res.getDisplayMetrics().density * MIN_WIDTH + 0.5f);
+ mPaint.setAntiAlias(true);
+ final TypedArray a = context.obtainStyledAttributes(
+ com.android.internal.R.styleable.EdgeEffect);
+ final int themeColor = a.getColor(
+ com.android.internal.R.styleable.EdgeEffect_colorPrimaryLight, 0xff666666);
+ a.recycle();
+ mPaint.setColor((themeColor & 0xffffff) | 0x33000000);
+ mPaint.setStyle(Paint.Style.FILL);
+ mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
mInterpolator = new DecelerateInterpolator();
}
@@ -158,20 +137,13 @@ public class EdgeEffect {
* @param height Effect height in pixels
*/
public void setSize(int width, int height) {
- mWidth = width;
- mHeight = height;
- }
+ final float r = width * 0.75f / SIN;
+ final float y = COS * r;
+ final float h = r - y;
+ mRadius = r;
+ mBaseGlowHeight = h;
- /**
- * Set the position of this edge effect in pixels. This position is
- * only used by {@link #getBounds(boolean)}.
- *
- * @param x The position of the edge effect on the X axis
- * @param y The position of the edge effect on the Y axis
- */
- void setPosition(int x, int y) {
- mX = x;
- mY = y;
+ mBounds.set(mBounds.left, mBounds.top, width, (int) Math.min(height, h));
}
/**
@@ -199,17 +171,38 @@ public class EdgeEffect {
* The host view should always {@link android.view.View#invalidate()} after this
* and draw the results accordingly.
*
+ * <p>Views using EdgeEffect should favor {@link #onPull(float, float)} when the displacement
+ * of the pull point is known.</p>
+ *
* @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
* 1.f (full length of the view) or negative values to express change
* back toward the edge reached to initiate the effect.
*/
public void onPull(float deltaDistance) {
+ onPull(deltaDistance, 0.5f);
+ }
+
+ /**
+ * A view should call this when content is pulled away from an edge by the user.
+ * This will update the state of the current visual effect and its associated animation.
+ * The host view should always {@link android.view.View#invalidate()} after this
+ * and draw the results accordingly.
+ *
+ * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
+ * 1.f (full length of the view) or negative values to express change
+ * back toward the edge reached to initiate the effect.
+ * @param displacement The displacement from the starting side of the effect of the point
+ * initiating the pull. In the case of touch this is the finger position.
+ * Values may be from 0-1.
+ */
+ public void onPull(float deltaDistance, float displacement) {
final long now = AnimationUtils.currentAnimationTimeMillis();
+ mTargetDisplacement = displacement;
if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
return;
}
if (mState != STATE_PULL) {
- mGlowScaleY = PULL_GLOW_BEGIN;
+ mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY);
}
mState = STATE_PULL;
@@ -217,30 +210,20 @@ public class EdgeEffect {
mDuration = PULL_TIME;
mPullDistance += deltaDistance;
- float distance = Math.abs(mPullDistance);
-
- mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, MAX_ALPHA));
- mEdgeScaleY = mEdgeScaleYStart = Math.max(
- HELD_EDGE_SCALE_Y, Math.min(distance * PULL_DISTANCE_EDGE_FACTOR, 1.f));
+ final float absdd = Math.abs(deltaDistance);
mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
- mGlowAlpha +
- (Math.abs(deltaDistance) * PULL_DISTANCE_ALPHA_GLOW_FACTOR));
+ mGlowAlpha + (absdd * PULL_DISTANCE_ALPHA_GLOW_FACTOR));
- float glowChange = Math.abs(deltaDistance);
- if (deltaDistance > 0 && mPullDistance < 0) {
- glowChange = -glowChange;
- }
if (mPullDistance == 0) {
- mGlowScaleY = 0;
- }
+ mGlowScaleY = mGlowScaleYStart = 0;
+ } else {
+ final float scale = Math.max(0, 1 - 1 /
+ FloatMath.sqrt(Math.abs(mPullDistance) * mBounds.height()) - 0.3f) / 0.7f;
- // Do not allow glow to get larger than MAX_GLOW_HEIGHT.
- mGlowScaleY = mGlowScaleYStart = Math.min(MAX_GLOW_HEIGHT, Math.max(
- 0, mGlowScaleY + glowChange * PULL_DISTANCE_GLOW_FACTOR));
+ mGlowScaleY = mGlowScaleYStart = scale;
+ }
- mEdgeAlphaFinish = mEdgeAlpha;
- mEdgeScaleYFinish = mEdgeScaleY;
mGlowAlphaFinish = mGlowAlpha;
mGlowScaleYFinish = mGlowScaleY;
}
@@ -259,13 +242,9 @@ public class EdgeEffect {
}
mState = STATE_RECEDE;
- mEdgeAlphaStart = mEdgeAlpha;
- mEdgeScaleYStart = mEdgeScaleY;
mGlowAlphaStart = mGlowAlpha;
mGlowScaleYStart = mGlowScaleY;
- mEdgeAlphaFinish = 0.f;
- mEdgeScaleYFinish = 0.f;
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = 0.f;
@@ -290,30 +269,21 @@ public class EdgeEffect {
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mDuration = 0.15f + (velocity * 0.02f);
- // The edge should always be at least partially visible, regardless
- // of velocity.
- mEdgeAlphaStart = 0.f;
- mEdgeScaleY = mEdgeScaleYStart = 0.f;
// The glow depends more on the velocity, and therefore starts out
// nearly invisible.
mGlowAlphaStart = 0.3f;
- mGlowScaleYStart = 0.f;
+ mGlowScaleYStart = Math.max(mGlowScaleY, 0.f);
- // Factor the velocity by 8. Testing on device shows this works best to
- // reflect the strength of the user's scrolling.
- mEdgeAlphaFinish = Math.max(0, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1));
- // Edge should never get larger than the size of its asset.
- mEdgeScaleYFinish = Math.max(
- HELD_EDGE_SCALE_Y, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1.f));
// Growth for the size of the glow should be quadratic to properly
// respond
// to a user's scrolling speed. The faster the scrolling speed, the more
// intense the effect should be for both the size and the saturation.
- mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f), 1.75f);
+ mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f) / 2, 1.f);
// Alpha should change for the glow as well as size.
mGlowAlphaFinish = Math.max(
mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
+ mTargetDisplacement = 0.5f;
}
@@ -330,52 +300,40 @@ public class EdgeEffect {
public boolean draw(Canvas canvas) {
update();
- mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255));
+ final int count = canvas.save();
- int glowBottom = (int) Math.min(
- mGlowHeight * mGlowScaleY * mGlowHeight / mGlowWidth * 0.6f,
- mGlowHeight * MAX_GLOW_HEIGHT);
- if (mWidth < mMinWidth) {
- // Center the glow and clip it.
- int glowLeft = (mWidth - mMinWidth)/2;
- mGlow.setBounds(glowLeft, 0, mWidth - glowLeft, glowBottom);
- } else {
- // Stretch the glow to fit.
- mGlow.setBounds(0, 0, mWidth, glowBottom);
- }
+ final float y = mBounds.height();
+ final float centerY = y - mRadius;
+ final float centerX = mBounds.centerX();
- mGlow.draw(canvas);
+ mArcRect.set(centerX - mRadius, centerY - mRadius, centerX + mRadius, centerY + mRadius);
+ canvas.scale(1.f, Math.min(mGlowScaleY, 1.f), centerX, 0);
- mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255));
+ final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f;
+ float translateX = mBounds.width() * displacement / 2;
- int edgeBottom = (int) (mEdgeHeight * mEdgeScaleY);
- if (mWidth < mMinWidth) {
- // Center the edge and clip it.
- int edgeLeft = (mWidth - mMinWidth)/2;
- mEdge.setBounds(edgeLeft, 0, mWidth - edgeLeft, edgeBottom);
- } else {
- // Stretch the edge to fit.
- mEdge.setBounds(0, 0, mWidth, edgeBottom);
- }
- mEdge.draw(canvas);
+ canvas.clipRect(Float.MIN_VALUE, mBounds.top,
+ Float.MAX_VALUE, Float.MAX_VALUE);
+ canvas.translate(translateX, 0);
+ canvas.drawArc(mArcRect, 45, 90, true, mPaint);
+ canvas.restoreToCount(count);
- if (mState == STATE_RECEDE && glowBottom == 0 && edgeBottom == 0) {
+ boolean oneLastFrame = false;
+ if (mState == STATE_RECEDE && mGlowScaleY == 0) {
mState = STATE_IDLE;
+ oneLastFrame = true;
}
- return mState != STATE_IDLE;
+ return mState != STATE_IDLE || oneLastFrame;
}
/**
- * Returns the bounds of the edge effect.
- *
- * @hide
+ * Return the maximum height that the edge effect will be drawn at given the original
+ * {@link #setSize(int, int) input size}.
+ * @return The maximum height of the edge effect
*/
- public Rect getBounds(boolean reverse) {
- mBounds.set(0, 0, mWidth, mMaxEffectHeight);
- mBounds.offset(mX, mY - (reverse ? mMaxEffectHeight : 0));
-
- return mBounds;
+ public int getMaxHeight() {
+ return (int) (mBounds.height() * MAX_GLOW_SCALE + 0.5f);
}
private void update() {
@@ -384,10 +342,9 @@ public class EdgeEffect {
final float interp = mInterpolator.getInterpolation(t);
- mEdgeAlpha = mEdgeAlphaStart + (mEdgeAlphaFinish - mEdgeAlphaStart) * interp;
- mEdgeScaleY = mEdgeScaleYStart + (mEdgeScaleYFinish - mEdgeScaleYStart) * interp;
mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
+ mDisplacement = (mDisplacement + mTargetDisplacement) / 2;
if (t >= 1.f - EPSILON) {
switch (mState) {
@@ -396,42 +353,17 @@ public class EdgeEffect {
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mDuration = RECEDE_TIME;
- mEdgeAlphaStart = mEdgeAlpha;
- mEdgeScaleYStart = mEdgeScaleY;
mGlowAlphaStart = mGlowAlpha;
mGlowScaleYStart = mGlowScaleY;
- // After absorb, the glow and edge should fade to nothing.
- mEdgeAlphaFinish = 0.f;
- mEdgeScaleYFinish = 0.f;
+ // After absorb, the glow should fade to nothing.
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = 0.f;
break;
case STATE_PULL:
- mState = STATE_PULL_DECAY;
- mStartTime = AnimationUtils.currentAnimationTimeMillis();
- mDuration = PULL_DECAY_TIME;
-
- mEdgeAlphaStart = mEdgeAlpha;
- mEdgeScaleYStart = mEdgeScaleY;
- mGlowAlphaStart = mGlowAlpha;
- mGlowScaleYStart = mGlowScaleY;
-
- // After pull, the glow and edge should fade to nothing.
- mEdgeAlphaFinish = 0.f;
- mEdgeScaleYFinish = 0.f;
- mGlowAlphaFinish = 0.f;
- mGlowScaleYFinish = 0.f;
+ // Hold in this state until explicitly released.
break;
case STATE_PULL_DECAY:
- // When receding, we want edge to decrease more slowly
- // than the glow.
- float factor = mGlowScaleYFinish != 0 ? 1
- / (mGlowScaleYFinish * mGlowScaleYFinish)
- : Float.MAX_VALUE;
- mEdgeScaleY = mEdgeScaleYStart +
- (mEdgeScaleYFinish - mEdgeScaleYStart) *
- interp * factor;
mState = STATE_RECEDE;
break;
case STATE_RECEDE:
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index b0a4e24..27d6b82 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -39,6 +39,7 @@ import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
@@ -94,6 +95,8 @@ import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.CursorAnchorInfo.CursorAnchorInfoBuilder;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
@@ -215,6 +218,8 @@ public class Editor {
private TextView mTextView;
+ final CursorAnchorInfoNotifier mCursorAnchorInfoNotifier = new CursorAnchorInfoNotifier();
+
Editor(TextView textView) {
mTextView = textView;
}
@@ -249,9 +254,13 @@ public class Editor {
// We had an active selection from before, start the selection mode.
startSelectionActionMode();
}
+
+ getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true);
}
void onDetachedFromWindow() {
+ getPositionListener().removeSubscriber(mCursorAnchorInfoNotifier);
+
if (mError != null) {
hideError();
}
@@ -780,7 +789,7 @@ public class Editor {
boolean parentPositionChanged, boolean parentScrolled);
}
- private boolean isPositionVisible(int positionX, int positionY) {
+ private boolean isPositionVisible(final float positionX, final float positionY) {
synchronized (TEMP_POSITION) {
final float[] position = TEMP_POSITION;
position[0] = positionX;
@@ -2134,7 +2143,8 @@ public class Editor {
private class PositionListener implements ViewTreeObserver.OnPreDrawListener {
// 3 handles
// 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others)
- private final int MAXIMUM_NUMBER_OF_LISTENERS = 6;
+ // 1 CursorAnchorInfoNotifier
+ private final int MAXIMUM_NUMBER_OF_LISTENERS = 7;
private TextViewPositionListener[] mPositionListeners =
new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS];
private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS];
@@ -2997,6 +3007,122 @@ public class Editor {
}
}
+ /**
+ * A listener to call {@link InputMethodManager#updateCursorAnchorInfo(View, CursorAnchorInfo)}
+ * while the input method is requesting the cursor/anchor position. Does nothing as long as
+ * {@link InputMethodManager#isWatchingCursor(View)} returns false.
+ */
+ private final class CursorAnchorInfoNotifier implements TextViewPositionListener {
+ final CursorAnchorInfoBuilder mSelectionInfoBuilder = new CursorAnchorInfoBuilder();
+ final int[] mTmpIntOffset = new int[2];
+ final Matrix mViewToScreenMatrix = new Matrix();
+
+ @Override
+ public void updatePosition(int parentPositionX, int parentPositionY,
+ boolean parentPositionChanged, boolean parentScrolled) {
+ final InputMethodState ims = mInputMethodState;
+ if (ims == null || ims.mBatchEditNesting > 0) {
+ return;
+ }
+ final InputMethodManager imm = InputMethodManager.peekInstance();
+ if (null == imm) {
+ return;
+ }
+ // Skip if the IME has not requested the cursor/anchor position.
+ if (!imm.isWatchingCursor(mTextView)) {
+ return;
+ }
+ Layout layout = mTextView.getLayout();
+ if (layout == null) {
+ return;
+ }
+
+ final CursorAnchorInfoBuilder builder = mSelectionInfoBuilder;
+ builder.reset();
+
+ final int selectionStart = mTextView.getSelectionStart();
+ builder.setSelectionRange(selectionStart, mTextView.getSelectionEnd());
+
+ // Construct transformation matrix from view local coordinates to screen coordinates.
+ mViewToScreenMatrix.set(mTextView.getMatrix());
+ mTextView.getLocationOnScreen(mTmpIntOffset);
+ mViewToScreenMatrix.postTranslate(mTmpIntOffset[0], mTmpIntOffset[1]);
+ builder.setMatrix(mViewToScreenMatrix);
+
+ final float viewportToContentHorizontalOffset =
+ mTextView.viewportToContentHorizontalOffset();
+ final float viewportToContentVerticalOffset =
+ mTextView.viewportToContentVerticalOffset();
+
+ final CharSequence text = mTextView.getText();
+ if (text instanceof Spannable) {
+ final Spannable sp = (Spannable) text;
+ int composingTextStart = EditableInputConnection.getComposingSpanStart(sp);
+ int composingTextEnd = EditableInputConnection.getComposingSpanEnd(sp);
+ if (composingTextEnd < composingTextStart) {
+ final int temp = composingTextEnd;
+ composingTextEnd = composingTextStart;
+ composingTextStart = temp;
+ }
+ final boolean hasComposingText =
+ (0 <= composingTextStart) && (composingTextStart < composingTextEnd);
+ if (hasComposingText) {
+ final CharSequence composingText = text.subSequence(composingTextStart,
+ composingTextEnd);
+ builder.setComposingText(composingTextStart, composingText);
+ }
+ for (int offset = composingTextStart; offset < composingTextEnd; offset++) {
+ if (offset < 0) {
+ continue;
+ }
+ final int line = layout.getLineForOffset(offset);
+ final float left = layout.getPrimaryHorizontal(offset)
+ + viewportToContentHorizontalOffset;
+ final float top = layout.getLineTop(line) + viewportToContentVerticalOffset;
+ // Here we are tentatively passing offset + 1 to calculate the other side of
+ // the primary horizontal to preserve as many positions as possible so that
+ // the IME can reconstruct the layout entirely. However, we should revisit this
+ // to have a clear specification about the relationship between the index of
+ // the character and its bounding box. See also the TODO comment below.
+ final float right = layout.getPrimaryHorizontal(offset + 1)
+ + viewportToContentHorizontalOffset;
+ final float bottom = layout.getLineBottom(line)
+ + viewportToContentVerticalOffset;
+ // Take TextView's padding and scroll into account.
+ if (isPositionVisible(left, top) && isPositionVisible(right, bottom)) {
+ // Here offset is the index in Java chars.
+ // TODO: We must have a well-defined specification. For example, how
+ // RTL, surrogate pairs, and composition letters are handled must be
+ // documented.
+ builder.addCharacterRect(offset, left, top, right, bottom);
+ }
+ }
+ }
+
+ // Treat selectionStart as the insertion point.
+ if (0 <= selectionStart) {
+ final int offset = selectionStart;
+ final int line = layout.getLineForOffset(offset);
+ final float insertionMarkerX = layout.getPrimaryHorizontal(offset)
+ + viewportToContentHorizontalOffset;
+ final float insertionMarkerTop = layout.getLineTop(line)
+ + viewportToContentVerticalOffset;
+ final float insertionMarkerBaseline = layout.getLineBaseline(line)
+ + viewportToContentVerticalOffset;
+ final float insertionMarkerBottom = layout.getLineBottom(line)
+ + viewportToContentVerticalOffset;
+ // Take TextView's padding and scroll into account.
+ if (isPositionVisible(insertionMarkerX, insertionMarkerTop) &&
+ isPositionVisible(insertionMarkerX, insertionMarkerBottom)) {
+ builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop,
+ insertionMarkerBaseline, insertionMarkerBottom);
+ }
+ }
+
+ imm.updateCursorAnchorInfo(mTextView, builder.build());
+ }
+ }
+
private abstract class HandleView extends View implements TextViewPositionListener {
protected Drawable mDrawable;
protected Drawable mDrawableLtr;
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 8511601..defc26c 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -104,14 +104,16 @@ import static java.lang.Math.min;
*
* <h4>Excess Space Distribution</h4>
*
- * GridLayout's distribution of excess space is based on <em>priority</em>
- * rather than <em>weight</em>.
+ * As of API 21, GridLayout's distribution of excess space accomodates the principle of weight.
+ * In the event that no weights are specified, the previous conventions are respected and
+ * columns and rows are taken as flexible if their views specify some form of alignment
+ * within their groups.
* <p>
- * A child's ability to stretch is inferred from the alignment properties of
- * its row and column groups (which are typically set by setting the
- * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters).
- * If alignment was defined along a given axis then the component
- * is taken as <em>flexible</em> in that direction. If no alignment was set,
+ * The flexibility of a view is therefore influenced by its alignment which is,
+ * in turn, typically defined by setting the
+ * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters.
+ * If either a weight or alignment were defined along a given axis then the component
+ * is taken as <em>flexible</em> in that direction. If no weight or alignment was set,
* the component is instead assumed to be <em>inflexible</em>.
* <p>
* Multiple components in the same row or column group are
@@ -122,12 +124,16 @@ import static java.lang.Math.min;
* elements is flexible if <em>one</em> of its elements is flexible.
* <p>
* To make a column stretch, make sure all of the components inside it define a
- * gravity. To prevent a column from stretching, ensure that one of the components
- * in the column does not define a gravity.
+ * weight or a gravity. To prevent a column from stretching, ensure that one of the components
+ * in the column does not define a weight or a gravity.
* <p>
* When the principle of flexibility does not provide complete disambiguation,
* GridLayout's algorithms favour rows and columns that are closer to its <em>right</em>
- * and <em>bottom</em> edges.
+ * and <em>bottom</em> edges. To be more precise, GridLayout treats each of its layout
+ * parameters as a constraint in the a set of variables that define the grid-lines along a
+ * given axis. During layout, GridLayout solves the constraints so as to return the unique
+ * solution to those constraints for which all variables are less-than-or-equal-to
+ * the corresponding value in any other valid solution.
*
* <h4>Interpretation of GONE</h4>
*
@@ -140,18 +146,6 @@ import static java.lang.Math.min;
* had never been added to it.
* These statements apply equally to rows as well as columns, and to groups of rows or columns.
*
- * <h5>Limitations</h5>
- *
- * GridLayout does not provide support for the principle of <em>weight</em>, as defined in
- * {@link LinearLayout.LayoutParams#weight}. In general, it is not therefore possible
- * to configure a GridLayout to distribute excess space between multiple components.
- * <p>
- * Some common use-cases may nevertheless be accommodated as follows.
- * To place equal amounts of space around a component in a cell group;
- * use {@link #CENTER} alignment (or {@link LayoutParams#setGravity(int) gravity}).
- * For complete control over excess space distribution in a row or column;
- * use a {@link LinearLayout} subview to hold the components in the associated cell group.
- * When using either of these techniques, bear in mind that cell groups may be defined to overlap.
* <p>
* See {@link GridLayout.LayoutParams} for a full description of the
* layout parameters used by GridLayout.
@@ -1018,6 +1012,8 @@ public class GridLayout extends ViewGroup {
LayoutParams lp = getLayoutParams(c);
if (firstPass) {
measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height);
+ mHorizontalAxis.recordOriginalMeasurement(i);
+ mVerticalAxis.recordOriginalMeasurement(i);
} else {
boolean horizontal = (mOrientation == HORIZONTAL);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
@@ -1245,6 +1241,11 @@ public class GridLayout extends ViewGroup {
public int[] locations;
public boolean locationsValid = false;
+ public boolean hasWeights;
+ public boolean hasWeightsValid = false;
+ public int[] originalMeasurements;
+ public int[] deltas;
+
boolean orderPreserved = DEFAULT_ORDER_PRESERVED;
private MutableInt parentMin = new MutableInt(0);
@@ -1321,7 +1322,10 @@ public class GridLayout extends ViewGroup {
// we must include views that are GONE here, see introductory javadoc
LayoutParams lp = getLayoutParams(c);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
- groupBounds.getValue(i).include(GridLayout.this, c, spec, this);
+ int size = (spec.weight == 0) ?
+ getMeasurementIncludingMargin(c, horizontal) :
+ getOriginalMeasurements()[i] + getDeltas()[i];
+ groupBounds.getValue(i).include(GridLayout.this, c, spec, this, size);
}
}
@@ -1693,8 +1697,94 @@ public class GridLayout extends ViewGroup {
return trailingMargins;
}
- private void computeLocations(int[] a) {
+ private void solve(int[] a) {
solve(getArcs(), a);
+ }
+
+ private boolean computeHasWeights() {
+ for (int i = 0, N = getChildCount(); i < N; i++) {
+ LayoutParams lp = getLayoutParams(getChildAt(i));
+ Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+ if (spec.weight != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean hasWeights() {
+ if (!hasWeightsValid) {
+ hasWeights = computeHasWeights();
+ hasWeightsValid = true;
+ }
+ return hasWeights;
+ }
+
+ public int[] getOriginalMeasurements() {
+ if (originalMeasurements == null) {
+ originalMeasurements = new int[getChildCount()];
+ }
+ return originalMeasurements;
+ }
+
+ private void recordOriginalMeasurement(int i) {
+ if (hasWeights()) {
+ getOriginalMeasurements()[i] = getMeasurementIncludingMargin(getChildAt(i), horizontal);
+ }
+ }
+
+ public int[] getDeltas() {
+ if (deltas == null) {
+ deltas = new int[getChildCount()];
+ }
+ return deltas;
+ }
+
+ private void shareOutDelta() {
+ int totalDelta = 0;
+ float totalWeight = 0;
+ for (int i = 0, N = getChildCount(); i < N; i++) {
+ View c = getChildAt(i);
+ LayoutParams lp = getLayoutParams(c);
+ Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+ float weight = spec.weight;
+ if (weight != 0) {
+ int delta = getMeasurement(c, horizontal) - getOriginalMeasurements()[i];
+ totalDelta += delta;
+ totalWeight += weight;
+ }
+ }
+ for (int i = 0, N = getChildCount(); i < N; i++) {
+ LayoutParams lp = getLayoutParams(getChildAt(i));
+ Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+ float weight = spec.weight;
+ if (weight != 0) {
+ int delta = Math.round((weight * totalDelta / totalWeight));
+ deltas[i] = delta;
+ // the two adjustments below are to counter the above rounding and avoid off-by-ones at the end
+ totalDelta -= delta;
+ totalWeight -= weight;
+ }
+ }
+ }
+
+ private void solveAndDistributeSpace(int[] a) {
+ Arrays.fill(getDeltas(), 0);
+ solve(a);
+ shareOutDelta();
+ arcsValid = false;
+ forwardLinksValid = false;
+ backwardLinksValid = false;
+ groupBoundsValid = false;
+ solve(a);
+ }
+
+ private void computeLocations(int[] a) {
+ if (!hasWeights()) {
+ solve(a);
+ } else {
+ solveAndDistributeSpace(a);
+ }
if (!orderPreserved) {
// Solve returns the smallest solution to the constraint system for which all
// values are positive. One value is therefore zero - though if the row/col
@@ -1777,6 +1867,10 @@ public class GridLayout extends ViewGroup {
locations = null;
+ originalMeasurements = null;
+ deltas = null;
+ hasWeightsValid = false;
+
invalidateValues();
}
@@ -1810,6 +1904,9 @@ public class GridLayout extends ViewGroup {
* both aspects of alignment within the cell group. It is also possible to specify a child's
* alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)}
* method.
+ * <p>
+ * The weight property is also included in Spec and specifies the proportion of any
+ * excess space that is due to the associated view.
*
* <h4>WRAP_CONTENT and MATCH_PARENT</h4>
*
@@ -1851,9 +1948,11 @@ public class GridLayout extends ViewGroup {
* <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li>
* <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li>
* <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li>
+ * <li>{@link #rowSpec}<code>.weight</code> = 0 </li>
* <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li>
* <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li>
* <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li>
+ * <li>{@link #columnSpec}<code>.weight</code> = 0 </li>
* </ul>
*
* See {@link GridLayout} for a more complete description of the conventions
@@ -1861,8 +1960,10 @@ public class GridLayout extends ViewGroup {
*
* @attr ref android.R.styleable#GridLayout_Layout_layout_row
* @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan
+ * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight
* @attr ref android.R.styleable#GridLayout_Layout_layout_column
* @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan
+ * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight
* @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
*/
public static class LayoutParams extends MarginLayoutParams {
@@ -1889,9 +1990,11 @@ public class GridLayout extends ViewGroup {
private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column;
private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan;
+ private static final int COLUMN_WEIGHT = R.styleable.GridLayout_Layout_layout_columnWeight;
private static final int ROW = R.styleable.GridLayout_Layout_layout_row;
private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan;
+ private static final int ROW_WEIGHT = R.styleable.GridLayout_Layout_layout_rowWeight;
private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity;
@@ -2034,11 +2137,13 @@ public class GridLayout extends ViewGroup {
int column = a.getInt(COLUMN, DEFAULT_COLUMN);
int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE);
- this.columnSpec = spec(column, colSpan, getAlignment(gravity, true));
+ float colWeight = a.getFloat(COLUMN_WEIGHT, Spec.DEFAULT_WEIGHT);
+ this.columnSpec = spec(column, colSpan, getAlignment(gravity, true), colWeight);
int row = a.getInt(ROW, DEFAULT_ROW);
int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE);
- this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false));
+ float rowWeight = a.getFloat(ROW_WEIGHT, Spec.DEFAULT_WEIGHT);
+ this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), rowWeight);
} finally {
a.recycle();
}
@@ -2273,10 +2378,9 @@ public class GridLayout extends ViewGroup {
return before - a.getAlignmentValue(c, size, gl.getLayoutMode());
}
- protected final void include(GridLayout gl, View c, Spec spec, Axis axis) {
+ protected final void include(GridLayout gl, View c, Spec spec, Axis axis, int size) {
this.flexibility &= spec.getFlexibility();
boolean horizontal = axis.horizontal;
- int size = gl.getMeasurementIncludingMargin(c, horizontal);
Alignment alignment = gl.getAlignment(spec.alignment, horizontal);
// todo test this works correctly when the returned value is UNDEFINED
int before = alignment.getAlignmentValue(c, size, gl.getLayoutMode());
@@ -2401,36 +2505,43 @@ public class GridLayout extends ViewGroup {
* <li>{@link #spec(int, int)}</li>
* <li>{@link #spec(int, Alignment)}</li>
* <li>{@link #spec(int, int, Alignment)}</li>
+ * <li>{@link #spec(int, float)}</li>
+ * <li>{@link #spec(int, int, float)}</li>
+ * <li>{@link #spec(int, Alignment, float)}</li>
+ * <li>{@link #spec(int, int, Alignment, float)}</li>
* </ul>
*
*/
public static class Spec {
static final Spec UNDEFINED = spec(GridLayout.UNDEFINED);
+ static final float DEFAULT_WEIGHT = 0;
final boolean startDefined;
final Interval span;
final Alignment alignment;
+ final float weight;
- private Spec(boolean startDefined, Interval span, Alignment alignment) {
+ private Spec(boolean startDefined, Interval span, Alignment alignment, float weight) {
this.startDefined = startDefined;
this.span = span;
this.alignment = alignment;
+ this.weight = weight;
}
- private Spec(boolean startDefined, int start, int size, Alignment alignment) {
- this(startDefined, new Interval(start, start + size), alignment);
+ private Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) {
+ this(startDefined, new Interval(start, start + size), alignment, weight);
}
final Spec copyWriteSpan(Interval span) {
- return new Spec(startDefined, span, alignment);
+ return new Spec(startDefined, span, alignment, weight);
}
final Spec copyWriteAlignment(Alignment alignment) {
- return new Spec(startDefined, span, alignment);
+ return new Spec(startDefined, span, alignment, weight);
}
final int getFlexibility() {
- return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH;
+ return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH;
}
/**
@@ -2478,6 +2589,7 @@ public class GridLayout extends ViewGroup {
* <ul>
* <li> {@code spec.span = [start, start + size]} </li>
* <li> {@code spec.alignment = alignment} </li>
+ * <li> {@code spec.weight = weight} </li>
* </ul>
* <p>
* To leave the start index undefined, use the value {@link #UNDEFINED}.
@@ -2485,9 +2597,55 @@ public class GridLayout extends ViewGroup {
* @param start the start
* @param size the size
* @param alignment the alignment
+ * @param weight the weight
+ */
+ public static Spec spec(int start, int size, Alignment alignment, float weight) {
+ return new Spec(start != UNDEFINED, start, size, alignment, weight);
+ }
+
+ /**
+ * Equivalent to: {@code spec(start, 1, alignment, weight)}.
+ *
+ * @param start the start
+ * @param alignment the alignment
+ * @param weight the weight
+ */
+ public static Spec spec(int start, Alignment alignment, float weight) {
+ return spec(start, 1, alignment, weight);
+ }
+
+ /**
+ * Equivalent to: {@code spec(start, 1, default_alignment, weight)} -
+ * where {@code default_alignment} is specified in
+ * {@link android.widget.GridLayout.LayoutParams}.
+ *
+ * @param start the start
+ * @param size the size
+ * @param weight the weight
+ */
+ public static Spec spec(int start, int size, float weight) {
+ return spec(start, size, UNDEFINED_ALIGNMENT, weight);
+ }
+
+ /**
+ * Equivalent to: {@code spec(start, 1, weight)}.
+ *
+ * @param start the start
+ * @param weight the weight
+ */
+ public static Spec spec(int start, float weight) {
+ return spec(start, 1, weight);
+ }
+
+ /**
+ * Equivalent to: {@code spec(start, size, alignment, 0f)}.
+ *
+ * @param start the start
+ * @param size the size
+ * @param alignment the alignment
*/
public static Spec spec(int start, int size, Alignment alignment) {
- return new Spec(start != UNDEFINED, start, size, alignment);
+ return spec(start, size, alignment, Spec.DEFAULT_WEIGHT);
}
/**
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 25d4f42..0c65c50 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -616,12 +616,14 @@ public class HorizontalScrollView extends FrameLayout {
if (canOverscroll) {
final int pulledToX = oldX + deltaX;
if (pulledToX < 0) {
- mEdgeGlowLeft.onPull((float) deltaX / getWidth());
+ mEdgeGlowLeft.onPull((float) deltaX / getWidth(),
+ 1.f - ev.getY(activePointerIndex) / getHeight());
if (!mEdgeGlowRight.isFinished()) {
mEdgeGlowRight.onRelease();
}
} else if (pulledToX > range) {
- mEdgeGlowRight.onPull((float) deltaX / getWidth());
+ mEdgeGlowRight.onPull((float) deltaX / getWidth(),
+ ev.getY(activePointerIndex) / getHeight());
if (!mEdgeGlowLeft.isFinished()) {
mEdgeGlowLeft.onRelease();
}
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index eedacb5..572302a 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -664,7 +664,7 @@ public class ImageView extends View {
InputStream stream = null;
try {
stream = mContext.getContentResolver().openInputStream(mUri);
- d = Drawable.createFromStreamThemed(stream, null, mContext.getTheme());
+ d = Drawable.createFromStream(stream, null);
} catch (Exception e) {
Log.w("ImageView", "Unable to open content: " + mUri, e);
} finally {
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index f7e81b8..b49938c 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -357,9 +357,8 @@ public class ProgressBar extends View {
Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
shapeDrawable.getPaint().setShader(bitmapShader);
- // Ensure the color filter and tint are propagated.
- shapeDrawable.setTint(bitmap.getTint());
- shapeDrawable.setTintMode(bitmap.getTintMode());
+ // Ensure the tint and filter are propagated in the correct order.
+ shapeDrawable.setTint(bitmap.getTint(), bitmap.getTintMode());
shapeDrawable.setColorFilter(bitmap.getColorFilter());
return clip ? new ClipDrawable(
@@ -1066,21 +1065,30 @@ public class ProgressBar extends View {
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
- Drawable d = mCurrentDrawable;
+ drawTrack(canvas);
+ }
+
+ /**
+ * Draws the progress bar track.
+ */
+ void drawTrack(Canvas canvas) {
+ final Drawable d = mCurrentDrawable;
if (d != null) {
// Translate canvas so a indeterminate circular progress bar with padding
// rotates properly in its animation
- canvas.save();
+ final int saveCount = canvas.save();
+
if(isLayoutRtl() && mMirrorForRtl) {
canvas.translate(getWidth() - mPaddingRight, mPaddingTop);
canvas.scale(-1.0f, 1.0f);
} else {
canvas.translate(mPaddingLeft, mPaddingTop);
}
- long time = getDrawingTime();
+
+ final long time = getDrawingTime();
if (mHasAnimation) {
mAnimation.getTransformation(time, mTransformation);
- float scale = mTransformation.getAlpha();
+ final float scale = mTransformation.getAlpha();
try {
mInDrawing = true;
d.setLevel((int) (scale * MAX_LEVEL));
@@ -1089,8 +1097,10 @@ public class ProgressBar extends View {
}
postInvalidateOnAnimation();
}
+
d.draw(canvas);
- canvas.restore();
+ canvas.restoreToCount(saveCount);
+
if (mShouldStartAnimationDrawable && d instanceof Animatable) {
((Animatable) d).start();
mShouldStartAnimationDrawable = false;
diff --git a/core/java/android/widget/RtlSpacingHelper.java b/core/java/android/widget/RtlSpacingHelper.java
new file mode 100644
index 0000000..f6b116f
--- /dev/null
+++ b/core/java/android/widget/RtlSpacingHelper.java
@@ -0,0 +1,91 @@
+/*
+ * 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;
+
+/**
+ * RtlSpacingHelper manages the relationship between left/right and start/end for views
+ * that need to maintain both absolute and relative settings for a form of spacing similar
+ * to view padding.
+ */
+class RtlSpacingHelper {
+ public static final int UNDEFINED = Integer.MIN_VALUE;
+
+ private int mLeft = 0;
+ private int mRight = 0;
+ private int mStart = UNDEFINED;
+ private int mEnd = UNDEFINED;
+ private int mExplicitLeft = 0;
+ private int mExplicitRight = 0;
+
+ private boolean mIsRtl = false;
+ private boolean mIsRelative = false;
+
+ public int getLeft() {
+ return mLeft;
+ }
+
+ public int getRight() {
+ return mRight;
+ }
+
+ public int getStart() {
+ return mIsRtl ? mRight : mLeft;
+ }
+
+ public int getEnd() {
+ return mIsRtl ? mLeft : mRight;
+ }
+
+ public void setRelative(int start, int end) {
+ mStart = start;
+ mEnd = end;
+ mIsRelative = true;
+ if (mIsRtl) {
+ if (end != UNDEFINED) mLeft = end;
+ if (start != UNDEFINED) mRight = start;
+ } else {
+ if (start != UNDEFINED) mLeft = start;
+ if (end != UNDEFINED) mRight = end;
+ }
+ }
+
+ public void setAbsolute(int left, int right) {
+ mIsRelative = false;
+ if (left != UNDEFINED) mLeft = mExplicitLeft = left;
+ if (right != UNDEFINED) mRight = mExplicitRight = right;
+ }
+
+ public void setDirection(boolean isRtl) {
+ if (isRtl == mIsRtl) {
+ return;
+ }
+ mIsRtl = isRtl;
+ if (mIsRelative) {
+ if (isRtl) {
+ mLeft = mEnd != UNDEFINED ? mEnd : mExplicitLeft;
+ mRight = mStart != UNDEFINED ? mStart : mExplicitRight;
+ } else {
+ mLeft = mStart != UNDEFINED ? mStart : mExplicitLeft;
+ mRight = mEnd != UNDEFINED ? mEnd : mExplicitRight;
+ }
+ } else {
+ mLeft = mExplicitLeft;
+ mRight = mExplicitRight;
+ }
+ }
+}
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 0fa75a6..fd04890 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -669,12 +669,14 @@ public class ScrollView extends FrameLayout {
} else if (canOverscroll) {
final int pulledToY = oldY + deltaY;
if (pulledToY < 0) {
- mEdgeGlowTop.onPull((float) deltaY / getHeight());
+ mEdgeGlowTop.onPull((float) deltaY / getHeight(),
+ ev.getX(activePointerIndex) / getWidth());
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
} else if (pulledToY > range) {
- mEdgeGlowBottom.onPull((float) deltaY / getHeight());
+ mEdgeGlowBottom.onPull((float) deltaY / getHeight(),
+ 1.f - ev.getX(activePointerIndex) / getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java
index 0203301..c8917e0 100644
--- a/core/java/android/widget/SuggestionsAdapter.java
+++ b/core/java/android/widget/SuggestionsAdapter.java
@@ -574,7 +574,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
throw new FileNotFoundException("Failed to open " + uri);
}
try {
- return Drawable.createFromStreamThemed(stream, null, mContext.getTheme());
+ return Drawable.createFromStream(stream, null);
} finally {
try {
stream.close();
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index 08af4de..c5c6e64 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -22,9 +22,11 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
+import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.text.Layout;
import android.text.StaticLayout;
@@ -85,6 +87,7 @@ public class Switch extends CompoundButton {
private int mThumbTextPadding;
private int mSwitchMinWidth;
private int mSwitchPadding;
+ private boolean mSplitTrack;
private CharSequence mTextOn;
private CharSequence mTextOff;
@@ -174,13 +177,13 @@ public class Switch extends CompoundButton {
super(context, attrs, defStyleAttr, defStyleRes);
mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
- Resources res = getResources();
+
+ final Resources res = getResources();
mTextPaint.density = res.getDisplayMetrics().density;
mTextPaint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);
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);
mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn);
@@ -191,15 +194,16 @@ public class Switch extends CompoundButton {
com.android.internal.R.styleable.Switch_switchMinWidth, 0);
mSwitchPadding = a.getDimensionPixelSize(
com.android.internal.R.styleable.Switch_switchPadding, 0);
+ mSplitTrack = a.getBoolean(com.android.internal.R.styleable.Switch_splitTrack, false);
- int appearance = a.getResourceId(
+ final int appearance = a.getResourceId(
com.android.internal.R.styleable.Switch_switchTextAppearance, 0);
if (appearance != 0) {
setSwitchTextAppearance(context, appearance);
}
a.recycle();
- ViewConfiguration config = ViewConfiguration.get(context);
+ final ViewConfiguration config = ViewConfiguration.get(context);
mTouchSlop = config.getScaledTouchSlop();
mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
@@ -469,6 +473,29 @@ public class Switch extends CompoundButton {
}
/**
+ * Specifies whether the track should be split by the thumb. When true,
+ * the thumb's optical bounds will be clipped out of the track drawable,
+ * then the thumb will be drawn into the resulting gap.
+ *
+ * @param splitTrack Whether the track should be split by the thumb
+ *
+ * @attr ref android.R.styleable#Switch_splitTrack
+ */
+ public void setSplitTrack(boolean splitTrack) {
+ mSplitTrack = splitTrack;
+ invalidate();
+ }
+
+ /**
+ * Returns whether the track should be split by the thumb.
+ *
+ * @attr ref android.R.styleable#Switch_splitTrack
+ */
+ public boolean getSplitTrack() {
+ return mSplitTrack;
+ }
+
+ /**
* Returns the text displayed when the button is in the checked state.
*
* @attr ref android.R.styleable#Switch_textOn
@@ -518,13 +545,15 @@ public class Switch extends CompoundButton {
mTrackDrawable.getPadding(mTempRect);
- final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth());
+ final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
+ + mThumbTextPadding * 2;
+ mThumbWidth = Math.max(maxTextWidth, mThumbDrawable.getIntrinsicWidth());
+
final int switchWidth = Math.max(mSwitchMinWidth,
- maxTextWidth * 2 + mThumbTextPadding * 4 + mTempRect.left + mTempRect.right);
+ 2 * mThumbWidth + mTempRect.left + mTempRect.right);
final int switchHeight = Math.max(mTrackDrawable.getIntrinsicHeight(),
mThumbDrawable.getIntrinsicHeight());
- mThumbWidth = maxTextWidth + mThumbTextPadding * 2;
mSwitchWidth = switchWidth;
mSwitchHeight = switchHeight;
@@ -637,6 +666,8 @@ public class Switch extends CompoundButton {
case MotionEvent.ACTION_CANCEL: {
if (mTouchMode == TOUCH_MODE_DRAGGING) {
stopDrag(ev);
+ // Allow super class to handle pressed state, etc.
+ super.onTouchEvent(ev);
return true;
}
mTouchMode = TOUCH_MODE_IDLE;
@@ -772,12 +803,12 @@ public class Switch extends CompoundButton {
}
@Override
- protected void onDraw(Canvas canvas) {
+ public void draw(Canvas c) {
final Rect tempRect = mTempRect;
final Drawable trackDrawable = mTrackDrawable;
final Drawable thumbDrawable = mThumbDrawable;
- // Draw the switch
+ // Layout the track.
final int switchLeft = mSwitchLeft;
final int switchTop = mSwitchTop;
final int switchRight = mSwitchRight;
@@ -786,40 +817,69 @@ public class Switch extends CompoundButton {
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();
+ // Layout the thumb.
thumbDrawable.getPadding(tempRect);
- int thumbLeft = switchInnerLeft - tempRect.left + thumbPos;
- int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + tempRect.right;
+ final int thumbLeft = switchInnerLeft - tempRect.left + thumbPos;
+ final int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + tempRect.right;
thumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
final Drawable background = getBackground();
- if (background != null && background.supportsHotspots()) {
+ if (background != null) {
background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom);
}
+ // Draw the background.
+ super.draw(c);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
- trackDrawable.draw(canvas);
+ final Rect tempRect = mTempRect;
+ final Drawable trackDrawable = mTrackDrawable;
+ final Drawable thumbDrawable = mThumbDrawable;
+ trackDrawable.getPadding(tempRect);
+
+ final int switchTop = mSwitchTop;
+ final int switchBottom = mSwitchBottom;
+ final int switchInnerLeft = mSwitchLeft + tempRect.left;
+ final int switchInnerTop = switchTop + tempRect.top;
+ final int switchInnerRight = mSwitchRight - tempRect.right;
+ final int switchInnerBottom = switchBottom - tempRect.bottom;
+
+ if (mSplitTrack) {
+ final Insets insets = thumbDrawable.getOpticalInsets();
+ thumbDrawable.copyBounds(tempRect);
+ tempRect.left += insets.left;
+ tempRect.right -= insets.right;
+
+ final int saveCount = canvas.save();
+ canvas.clipRect(tempRect, Op.DIFFERENCE);
+ trackDrawable.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ } else {
+ trackDrawable.draw(canvas);
+ }
final int saveCount = canvas.save();
canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
thumbDrawable.draw(canvas);
- final int drawableState[] = getDrawableState();
- if (mTextColors != null) {
- mTextPaint.setColor(mTextColors.getColorForState(drawableState, 0));
- }
- mTextPaint.drawableState = drawableState;
-
final Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
if (switchText != null) {
- final int left = (thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2;
+ final int drawableState[] = getDrawableState();
+ if (mTextColors != null) {
+ mTextPaint.setColor(mTextColors.getColorForState(drawableState, 0));
+ }
+ mTextPaint.drawableState = drawableState;
+
+ final Rect thumbBounds = thumbDrawable.getBounds();
+ final int left = (thumbBounds.left + thumbBounds.right) / 2 - switchText.getWidth() / 2;
final int top = (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2;
canvas.translate(left, top);
switchText.draw(canvas);
@@ -889,17 +949,30 @@ public class Switch extends CompoundButton {
protected void drawableStateChanged() {
super.drawableStateChanged();
- int[] myDrawableState = getDrawableState();
+ final int[] myDrawableState = getDrawableState();
- // Set the state of the Drawable
- // Drawable may be null when checked state is set from XML, from super constructor
- if (mThumbDrawable != null) mThumbDrawable.setState(myDrawableState);
- if (mTrackDrawable != null) mTrackDrawable.setState(myDrawableState);
+ if (mThumbDrawable != null) {
+ mThumbDrawable.setState(myDrawableState);
+ }
+
+ if (mTrackDrawable != null) {
+ mTrackDrawable.setState(myDrawableState);
+ }
invalidate();
}
@Override
+ public void invalidateDrawable(Drawable drawable) {
+ super.invalidateDrawable(drawable);
+
+ if (drawable == mThumbDrawable) {
+ // Handle changes to thumb width and height.
+ requestLayout();
+ }
+ }
+
+ @Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable;
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 8f073de..a4a9680 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8194,7 +8194,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final boolean isPassword = hasPasswordTransformationMethod();
info.setPassword(isPassword);
- if (!isPassword) {
+ if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
info.setText(getTextForAccessibility());
}
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 075feba..419c582 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -18,13 +18,17 @@
package android.widget;
import android.annotation.NonNull;
+import android.app.ActionBar;
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.Layout;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.CollapsibleActionView;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
@@ -32,7 +36,15 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.Window;
import com.android.internal.R;
+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;
+import com.android.internal.view.menu.SubMenuBuilder;
+import com.android.internal.widget.DecorToolbar;
+import com.android.internal.widget.ToolbarWidgetWrapper;
import java.util.ArrayList;
import java.util.List;
@@ -80,19 +92,33 @@ import java.util.List;
* layout is discouraged on API 21 devices and newer.</p>
*/
public class Toolbar extends ViewGroup {
+ private static final String TAG = "Toolbar";
+
private ActionMenuView mMenuView;
private TextView mTitleTextView;
private TextView mSubtitleTextView;
private ImageButton mNavButtonView;
private ImageView mLogoView;
+ private Drawable mCollapseIcon;
+ private ImageButton mCollapseButtonView;
+ View mExpandedActionView;
+
private int mTitleTextAppearance;
private int mSubtitleTextAppearance;
+ private int mNavButtonStyle;
+
+ private int mButtonGravity;
+
+ private int mMaxButtonHeight;
+
private int mTitleMarginStart;
private int mTitleMarginEnd;
private int mTitleMarginTop;
private int mTitleMarginBottom;
+ private final RtlSpacingHelper mContentInsets = new RtlSpacingHelper();
+
private int mGravity = Gravity.START | Gravity.CENTER_VERTICAL;
private CharSequence mTitleText;
@@ -101,6 +127,8 @@ public class Toolbar extends ViewGroup {
// Clear me after use.
private final ArrayList<View> mTempViews = new ArrayList<View>();
+ private final int[] mTempMargins = new int[2];
+
private OnMenuItemClickListener mOnMenuItemClickListener;
private final ActionMenuView.OnMenuItemClickListener mMenuViewItemClickListener =
@@ -114,6 +142,10 @@ public class Toolbar extends ViewGroup {
}
};
+ private ToolbarWidgetWrapper mWrapper;
+ private ActionMenuPresenter mOuterActionMenuPresenter;
+ private ExpandedActionViewMenuPresenter mExpandedMenuPresenter;
+
public Toolbar(Context context) {
this(context, null);
}
@@ -134,9 +166,11 @@ public class Toolbar extends ViewGroup {
mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0);
mSubtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0);
+ mNavButtonStyle = a.getResourceId(R.styleable.Toolbar_navigationButtonStyle, 0);
mGravity = a.getInteger(R.styleable.Toolbar_gravity, mGravity);
- mTitleMarginStart = mTitleMarginEnd = Math.max(0, a.getDimensionPixelOffset(
- R.styleable.Toolbar_titleMargins, -1));
+ mButtonGravity = a.getInteger(R.styleable.Toolbar_buttonGravity, Gravity.TOP);
+ mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom =
+ a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargins, 0);
final int marginStart = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginStart, -1);
if (marginStart >= 0) {
@@ -159,6 +193,28 @@ public class Toolbar extends ViewGroup {
mTitleMarginBottom = marginBottom;
}
+ mMaxButtonHeight = a.getDimensionPixelSize(R.styleable.Toolbar_maxButtonHeight, -1);
+
+ final int contentInsetStart =
+ a.getDimensionPixelOffset(R.styleable.Toolbar_contentInsetStart,
+ RtlSpacingHelper.UNDEFINED);
+ final int contentInsetEnd =
+ a.getDimensionPixelOffset(R.styleable.Toolbar_contentInsetEnd,
+ RtlSpacingHelper.UNDEFINED);
+ final int contentInsetLeft =
+ a.getDimensionPixelSize(R.styleable.Toolbar_contentInsetLeft, 0);
+ final int contentInsetRight =
+ a.getDimensionPixelSize(R.styleable.Toolbar_contentInsetRight, 0);
+
+ mContentInsets.setAbsolute(contentInsetLeft, contentInsetRight);
+
+ if (contentInsetStart != RtlSpacingHelper.UNDEFINED ||
+ contentInsetEnd != RtlSpacingHelper.UNDEFINED) {
+ mContentInsets.setRelative(contentInsetStart, contentInsetEnd);
+ }
+
+ mCollapseIcon = a.getDrawable(R.styleable.Toolbar_collapseIcon);
+
final CharSequence title = a.getText(R.styleable.Toolbar_title);
if (!TextUtils.isEmpty(title)) {
setTitle(title);
@@ -166,11 +222,17 @@ public class Toolbar extends ViewGroup {
final CharSequence subtitle = a.getText(R.styleable.Toolbar_subtitle);
if (!TextUtils.isEmpty(subtitle)) {
- setSubtitle(title);
+ setSubtitle(subtitle);
}
a.recycle();
}
+ @Override
+ public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ mContentInsets.setDirection(layoutDirection == LAYOUT_DIRECTION_RTL);
+ }
+
/**
* Set a logo drawable from a resource id.
*
@@ -184,6 +246,110 @@ public class Toolbar extends ViewGroup {
setLogo(getContext().getDrawable(resId));
}
+ /** @hide */
+ public boolean canShowOverflowMenu() {
+ return getVisibility() == VISIBLE && mMenuView != null && mMenuView.isOverflowReserved();
+ }
+
+ /**
+ * Check whether the overflow menu is currently showing. This may not reflect
+ * a pending show operation in progress.
+ *
+ * @return true if the overflow menu is currently showing
+ */
+ public boolean isOverflowMenuShowing() {
+ return mMenuView != null && mMenuView.isOverflowMenuShowing();
+ }
+
+ /** @hide */
+ public boolean isOverflowMenuShowPending() {
+ return mMenuView != null && mMenuView.isOverflowMenuShowPending();
+ }
+
+ /**
+ * Show the overflow items from the associated menu.
+ *
+ * @return true if the menu was able to be shown, false otherwise
+ */
+ public boolean showOverflowMenu() {
+ return mMenuView != null && mMenuView.showOverflowMenu();
+ }
+
+ /**
+ * Hide the overflow items from the associated menu.
+ *
+ * @return true if the menu was able to be hidden, false otherwise
+ */
+ public boolean hideOverflowMenu() {
+ return mMenuView != null && mMenuView.hideOverflowMenu();
+ }
+
+ /** @hide */
+ public void setMenu(MenuBuilder menu, ActionMenuPresenter outerPresenter) {
+ if (menu == null && mMenuView == null) {
+ return;
+ }
+
+ ensureMenuView();
+ final MenuBuilder oldMenu = mMenuView.peekMenu();
+ if (oldMenu == menu) {
+ return;
+ }
+
+ if (oldMenu != null) {
+ oldMenu.removeMenuPresenter(mOuterActionMenuPresenter);
+ oldMenu.removeMenuPresenter(mExpandedMenuPresenter);
+ }
+
+ final Context context = getContext();
+
+ if (mExpandedMenuPresenter == null) {
+ mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
+ }
+
+ outerPresenter.setExpandedActionViewsExclusive(true);
+ if (menu != null) {
+ menu.addMenuPresenter(outerPresenter);
+ menu.addMenuPresenter(mExpandedMenuPresenter);
+ } else {
+ outerPresenter.initForMenu(context, null);
+ mExpandedMenuPresenter.initForMenu(context, null);
+ outerPresenter.updateMenuView(true);
+ mExpandedMenuPresenter.updateMenuView(true);
+ }
+ mMenuView.setPresenter(outerPresenter);
+ mOuterActionMenuPresenter = outerPresenter;
+ }
+
+ /**
+ * Dismiss all currently showing popup menus, including overflow or submenus.
+ */
+ public void dismissPopupMenus() {
+ if (mMenuView != null) {
+ mMenuView.dismissPopupMenus();
+ }
+ }
+
+ /** @hide */
+ public boolean isTitleTruncated() {
+ if (mTitleTextView == null) {
+ return false;
+ }
+
+ final Layout titleLayout = mTitleTextView.getLayout();
+ if (titleLayout == null) {
+ return false;
+ }
+
+ final int lineCount = titleLayout.getLineCount();
+ for (int i = 0; i < lineCount; i++) {
+ if (titleLayout.getEllipsisCount(i) > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Set a logo drawable.
*
@@ -195,9 +361,7 @@ public class Toolbar extends ViewGroup {
*/
public void setLogo(Drawable drawable) {
if (drawable != null) {
- if (mLogoView == null) {
- mLogoView = new ImageView(getContext());
- }
+ ensureLogoView();
if (mLogoView.getParent() == null) {
addSystemView(mLogoView);
}
@@ -241,8 +405,8 @@ public class Toolbar extends ViewGroup {
* @param description Description to set
*/
public void setLogoDescription(CharSequence description) {
- if (!TextUtils.isEmpty(description) && mLogoView == null) {
- mLogoView = new ImageView(getContext());
+ if (!TextUtils.isEmpty(description)) {
+ ensureLogoView();
}
if (mLogoView != null) {
mLogoView.setContentDescription(description);
@@ -258,10 +422,48 @@ public class Toolbar extends ViewGroup {
return mLogoView != null ? mLogoView.getContentDescription() : null;
}
+ private void ensureLogoView() {
+ if (mLogoView == null) {
+ mLogoView = new ImageView(getContext());
+ }
+ }
+
/**
- * Return the current title displayed in the toolbar.
+ * Check whether this Toolbar is currently hosting an expanded action view.
+ *
+ * <p>An action view may be expanded either directly from the
+ * {@link android.view.MenuItem MenuItem} it belongs to or by user action. If the Toolbar
+ * has an expanded action view it can be collapsed using the {@link #collapseActionView()}
+ * method.</p>
*
- * @return The current title
+ * @return true if the Toolbar has an expanded action view
+ */
+ public boolean hasExpandedActionView() {
+ return mExpandedMenuPresenter != null &&
+ mExpandedMenuPresenter.mCurrentExpandedItem != null;
+ }
+
+ /**
+ * Collapse a currently expanded action view. If this Toolbar does not have an
+ * expanded action view this method has no effect.
+ *
+ * <p>An action view may be expanded either directly from the
+ * {@link android.view.MenuItem MenuItem} it belongs to or by user action.</p>
+ *
+ * @see #hasExpandedActionView()
+ */
+ public void collapseActionView() {
+ final MenuItemImpl item = mExpandedMenuPresenter == null ? null :
+ mExpandedMenuPresenter.mCurrentExpandedItem;
+ if (item != null) {
+ item.collapseActionView();
+ }
+ }
+
+ /**
+ * Returns the title of this toolbar.
+ *
+ * @return The current title.
*/
public CharSequence getTitle() {
return mTitleText;
@@ -292,6 +494,8 @@ public class Toolbar extends ViewGroup {
if (mTitleTextView == null) {
final Context context = getContext();
mTitleTextView = new TextView(context);
+ mTitleTextView.setSingleLine();
+ mTitleTextView.setEllipsize(TextUtils.TruncateAt.END);
mTitleTextView.setTextAppearance(context, mTitleTextAppearance);
}
if (mTitleTextView.getParent() == null) {
@@ -338,6 +542,8 @@ public class Toolbar extends ViewGroup {
if (mSubtitleTextView == null) {
final Context context = getContext();
mSubtitleTextView = new TextView(context);
+ mSubtitleTextView.setSingleLine();
+ mSubtitleTextView.setEllipsize(TextUtils.TruncateAt.END);
mSubtitleTextView.setTextAppearance(context, mSubtitleTextAppearance);
}
if (mSubtitleTextView.getParent() == null) {
@@ -353,6 +559,28 @@ public class Toolbar extends ViewGroup {
}
/**
+ * Sets the text color, size, style, hint color, and highlight color
+ * from the specified TextAppearance resource.
+ */
+ public void setTitleTextAppearance(Context context, int resId) {
+ mTitleTextAppearance = resId;
+ if (mTitleTextView != null) {
+ mTitleTextView.setTextAppearance(context, resId);
+ }
+ }
+
+ /**
+ * Sets the text color, size, style, hint color, and highlight color
+ * from the specified TextAppearance resource.
+ */
+ public void setSubtitleTextAppearance(Context context, int resId) {
+ mSubtitleTextAppearance = resId;
+ if (mSubtitleTextView != null) {
+ mSubtitleTextView.setTextAppearance(context, 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
@@ -368,6 +596,30 @@ public class Toolbar extends ViewGroup {
}
/**
+ * Set a content description for the navigation button if one is present. The content
+ * description will be read via screen readers or other accessibility systems to explain
+ * the action of the navigation button.
+ *
+ * @param description Content description to set
+ */
+ public void setNavigationContentDescription(CharSequence description) {
+ ensureNavButtonView();
+ mNavButtonView.setContentDescription(description);
+ }
+
+ /**
+ * Set a content description for the navigation button if one is present. The content
+ * description will be read via screen readers or other accessibility systems to explain
+ * the action of the navigation button.
+ *
+ * @param resId Resource ID of a content description string to set
+ */
+ public void setNavigationContentDescription(int resId) {
+ ensureNavButtonView();
+ mNavButtonView.setContentDescription(resId != 0 ? getContext().getText(resId) : null);
+ }
+
+ /**
* 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
@@ -453,12 +705,32 @@ public class Toolbar extends ViewGroup {
* @return The toolbar's Menu
*/
public Menu getMenu() {
+ ensureMenu();
+ return mMenuView.getMenu();
+ }
+
+ private void ensureMenu() {
+ ensureMenuView();
+ if (mMenuView.peekMenu() == null) {
+ // Initialize a new menu for the first time.
+ final MenuBuilder menu = (MenuBuilder) mMenuView.getMenu();
+ if (mExpandedMenuPresenter == null) {
+ mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
+ }
+ mMenuView.setExpandedActionViewsExclusive(true);
+ menu.addMenuPresenter(mExpandedMenuPresenter);
+ }
+ }
+
+ private void ensureMenuView() {
if (mMenuView == null) {
mMenuView = new ActionMenuView(getContext());
mMenuView.setOnMenuItemClickListener(mMenuViewItemClickListener);
+ final LayoutParams lp = generateDefaultLayoutParams();
+ lp.gravity = Gravity.END | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
+ mMenuView.setLayoutParams(lp);
addSystemView(mMenuView);
}
- return mMenuView.getMenu();
}
private MenuInflater getMenuInflater() {
@@ -489,9 +761,145 @@ public class Toolbar extends ViewGroup {
mOnMenuItemClickListener = listener;
}
+ /**
+ * Set the content insets for this toolbar relative to layout direction.
+ *
+ * <p>The content inset affects the valid area for Toolbar content other than
+ * the navigation button and menu. Insets define the minimum margin for these components
+ * and can be used to effectively align Toolbar content along well-known gridlines.</p>
+ *
+ * @param contentInsetStart Content inset for the toolbar starting edge
+ * @param contentInsetEnd Content inset for the toolbar ending edge
+ *
+ * @see #setContentInsetsAbsolute(int, int)
+ * @see #getContentInsetStart()
+ * @see #getContentInsetEnd()
+ * @see #getContentInsetLeft()
+ * @see #getContentInsetRight()
+ */
+ public void setContentInsetsRelative(int contentInsetStart, int contentInsetEnd) {
+ mContentInsets.setRelative(contentInsetStart, contentInsetEnd);
+ }
+
+ /**
+ * Get the starting content inset for this toolbar.
+ *
+ * <p>The content inset affects the valid area for Toolbar content other than
+ * the navigation button and menu. Insets define the minimum margin for these components
+ * and can be used to effectively align Toolbar content along well-known gridlines.</p>
+ *
+ * @return The starting content inset for this toolbar
+ *
+ * @see #setContentInsetsRelative(int, int)
+ * @see #setContentInsetsAbsolute(int, int)
+ * @see #getContentInsetEnd()
+ * @see #getContentInsetLeft()
+ * @see #getContentInsetRight()
+ */
+ public int getContentInsetStart() {
+ return mContentInsets.getStart();
+ }
+
+ /**
+ * Get the ending content inset for this toolbar.
+ *
+ * <p>The content inset affects the valid area for Toolbar content other than
+ * the navigation button and menu. Insets define the minimum margin for these components
+ * and can be used to effectively align Toolbar content along well-known gridlines.</p>
+ *
+ * @return The ending content inset for this toolbar
+ *
+ * @see #setContentInsetsRelative(int, int)
+ * @see #setContentInsetsAbsolute(int, int)
+ * @see #getContentInsetStart()
+ * @see #getContentInsetLeft()
+ * @see #getContentInsetRight()
+ */
+ public int getContentInsetEnd() {
+ return mContentInsets.getEnd();
+ }
+
+ /**
+ * Set the content insets for this toolbar.
+ *
+ * <p>The content inset affects the valid area for Toolbar content other than
+ * the navigation button and menu. Insets define the minimum margin for these components
+ * and can be used to effectively align Toolbar content along well-known gridlines.</p>
+ *
+ * @param contentInsetLeft Content inset for the toolbar's left edge
+ * @param contentInsetRight Content inset for the toolbar's right edge
+ *
+ * @see #setContentInsetsAbsolute(int, int)
+ * @see #getContentInsetStart()
+ * @see #getContentInsetEnd()
+ * @see #getContentInsetLeft()
+ * @see #getContentInsetRight()
+ */
+ public void setContentInsetsAbsolute(int contentInsetLeft, int contentInsetRight) {
+ mContentInsets.setAbsolute(contentInsetLeft, contentInsetRight);
+ }
+
+ /**
+ * Get the left content inset for this toolbar.
+ *
+ * <p>The content inset affects the valid area for Toolbar content other than
+ * the navigation button and menu. Insets define the minimum margin for these components
+ * and can be used to effectively align Toolbar content along well-known gridlines.</p>
+ *
+ * @return The left content inset for this toolbar
+ *
+ * @see #setContentInsetsRelative(int, int)
+ * @see #setContentInsetsAbsolute(int, int)
+ * @see #getContentInsetStart()
+ * @see #getContentInsetEnd()
+ * @see #getContentInsetRight()
+ */
+ public int getContentInsetLeft() {
+ return mContentInsets.getLeft();
+ }
+
+ /**
+ * Get the right content inset for this toolbar.
+ *
+ * <p>The content inset affects the valid area for Toolbar content other than
+ * the navigation button and menu. Insets define the minimum margin for these components
+ * and can be used to effectively align Toolbar content along well-known gridlines.</p>
+ *
+ * @return The right content inset for this toolbar
+ *
+ * @see #setContentInsetsRelative(int, int)
+ * @see #setContentInsetsAbsolute(int, int)
+ * @see #getContentInsetStart()
+ * @see #getContentInsetEnd()
+ * @see #getContentInsetLeft()
+ */
+ public int getContentInsetRight() {
+ return mContentInsets.getRight();
+ }
+
private void ensureNavButtonView() {
if (mNavButtonView == null) {
- mNavButtonView = new ImageButton(getContext(), null, R.attr.borderlessButtonStyle);
+ mNavButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle);
+ final LayoutParams lp = generateDefaultLayoutParams();
+ lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
+ mNavButtonView.setLayoutParams(lp);
+ }
+ }
+
+ private void ensureCollapseButtonView() {
+ if (mCollapseButtonView == null) {
+ mCollapseButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle);
+ mCollapseButtonView.setImageDrawable(mCollapseIcon);
+ final LayoutParams lp = generateDefaultLayoutParams();
+ lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
+ lp.mViewType = LayoutParams.EXPANDED;
+ mCollapseButtonView.setLayoutParams(lp);
+ mCollapseButtonView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ collapseActionView();
+ }
+ });
}
}
@@ -514,33 +922,121 @@ public class Toolbar extends ViewGroup {
super.onRestoreInstanceState(ss.getSuperState());
}
+ private void measureChildConstrained(View child, int parentWidthSpec, int widthUsed,
+ int parentHeightSpec, int heightUsed, int heightConstraint) {
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+ int childWidthSpec = getChildMeasureSpec(parentWidthSpec,
+ mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ + widthUsed, lp.width);
+ int childHeightSpec = getChildMeasureSpec(parentHeightSpec,
+ mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ + heightUsed, lp.height);
+
+ final int childHeightMode = MeasureSpec.getMode(childHeightSpec);
+ if (childHeightMode != MeasureSpec.EXACTLY && heightConstraint >= 0) {
+ final int size = childHeightMode != MeasureSpec.UNSPECIFIED ?
+ Math.min(MeasureSpec.getSize(childHeightSpec), heightConstraint) :
+ heightConstraint;
+ childHeightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
+ }
+ child.measure(childWidthSpec, childHeightSpec);
+ }
+
+ /**
+ * Returns the width + uncollapsed margins
+ */
+ private int measureChildCollapseMargins(View child,
+ int parentWidthMeasureSpec, int widthUsed,
+ int parentHeightMeasureSpec, int heightUsed, int[] collapsingMargins) {
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+ final int leftDiff = lp.leftMargin - collapsingMargins[0];
+ final int rightDiff = lp.rightMargin - collapsingMargins[1];
+ final int leftMargin = Math.max(0, leftDiff);
+ final int rightMargin = Math.max(0, rightDiff);
+ final int hMargins = leftMargin + rightMargin;
+ collapsingMargins[0] = Math.max(0, -leftDiff);
+ collapsingMargins[1] = Math.max(0, -rightDiff);
+
+ final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+ mPaddingLeft + mPaddingRight + hMargins + widthUsed, lp.width);
+ final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+ mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ + heightUsed, lp.height);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ return child.getMeasuredWidth() + hMargins;
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 0;
int height = 0;
int childState = 0;
+ final int[] collapsingMargins = mTempMargins;
+ final int marginStartIndex;
+ final int marginEndIndex;
+ if (isLayoutRtl()) {
+ marginStartIndex = 1;
+ marginEndIndex = 0;
+ } else {
+ marginStartIndex = 0;
+ marginEndIndex = 1;
+ }
+
// System views measure first.
+ int navWidth = 0;
if (shouldLayout(mNavButtonView)) {
- measureChildWithMargins(mNavButtonView, widthMeasureSpec, width, heightMeasureSpec, 0);
- width += mNavButtonView.getMeasuredWidth() + getHorizontalMargins(mNavButtonView);
+ measureChildConstrained(mNavButtonView, widthMeasureSpec, width, heightMeasureSpec, 0,
+ mMaxButtonHeight);
+ navWidth = mNavButtonView.getMeasuredWidth() + getHorizontalMargins(mNavButtonView);
height = Math.max(height, mNavButtonView.getMeasuredHeight() +
getVerticalMargins(mNavButtonView));
childState = combineMeasuredStates(childState, mNavButtonView.getMeasuredState());
}
+ if (shouldLayout(mCollapseButtonView)) {
+ measureChildConstrained(mCollapseButtonView, widthMeasureSpec, width,
+ heightMeasureSpec, 0, mMaxButtonHeight);
+ navWidth = mCollapseButtonView.getMeasuredWidth() +
+ getHorizontalMargins(mCollapseButtonView);
+ height = Math.max(height, mCollapseButtonView.getMeasuredHeight() +
+ getVerticalMargins(mCollapseButtonView));
+ childState = combineMeasuredStates(childState, mCollapseButtonView.getMeasuredState());
+ }
+
+ final int contentInsetStart = getContentInsetStart();
+ width += Math.max(contentInsetStart, navWidth);
+ collapsingMargins[marginStartIndex] = Math.max(0, contentInsetStart - navWidth);
+
+ int menuWidth = 0;
if (shouldLayout(mMenuView)) {
- measureChildWithMargins(mMenuView, widthMeasureSpec, width, heightMeasureSpec, 0);
- width += mMenuView.getMeasuredWidth() + getHorizontalMargins(mMenuView);
+ measureChildConstrained(mMenuView, widthMeasureSpec, width, heightMeasureSpec, 0,
+ mMaxButtonHeight);
+ menuWidth = mMenuView.getMeasuredWidth() + getHorizontalMargins(mMenuView);
height = Math.max(height, mMenuView.getMeasuredHeight() +
getVerticalMargins(mMenuView));
childState = combineMeasuredStates(childState, mMenuView.getMeasuredState());
}
+ final int contentInsetEnd = getContentInsetEnd();
+ width += Math.max(contentInsetEnd, menuWidth);
+ collapsingMargins[marginEndIndex] = Math.max(0, contentInsetEnd - menuWidth);
+
+ if (shouldLayout(mExpandedActionView)) {
+ width += measureChildCollapseMargins(mExpandedActionView, widthMeasureSpec, width,
+ heightMeasureSpec, 0, collapsingMargins);
+ height = Math.max(height, mExpandedActionView.getMeasuredHeight() +
+ getVerticalMargins(mExpandedActionView));
+ childState = combineMeasuredStates(childState, mExpandedActionView.getMeasuredState());
+ }
+
if (shouldLayout(mLogoView)) {
- measureChildWithMargins(mLogoView, widthMeasureSpec, width, heightMeasureSpec, 0);
- width += mLogoView.getMeasuredWidth() + getHorizontalMargins(mLogoView);
+ width += measureChildCollapseMargins(mLogoView, widthMeasureSpec, width,
+ heightMeasureSpec, 0, collapsingMargins);
height = Math.max(height, mLogoView.getMeasuredHeight() +
getVerticalMargins(mLogoView));
childState = combineMeasuredStates(childState, mLogoView.getMeasuredState());
@@ -551,17 +1047,18 @@ public class Toolbar extends ViewGroup {
final int titleVertMargins = mTitleMarginTop + mTitleMarginBottom;
final int titleHorizMargins = mTitleMarginStart + mTitleMarginEnd;
if (shouldLayout(mTitleTextView)) {
- measureChildWithMargins(mTitleTextView, widthMeasureSpec, width + titleHorizMargins,
- heightMeasureSpec, titleVertMargins);
+ titleWidth = measureChildCollapseMargins(mTitleTextView, widthMeasureSpec,
+ width + titleHorizMargins, heightMeasureSpec, titleVertMargins,
+ collapsingMargins);
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));
+ titleWidth = Math.max(titleWidth, measureChildCollapseMargins(mSubtitleTextView,
+ widthMeasureSpec, width + titleHorizMargins,
+ heightMeasureSpec, titleHeight + titleVertMargins,
+ collapsingMargins));
titleHeight += mSubtitleTextView.getMeasuredHeight() +
getVerticalMargins(mSubtitleTextView);
childState = combineMeasuredStates(childState, mSubtitleTextView.getMeasuredState());
@@ -574,13 +1071,13 @@ public class Toolbar extends ViewGroup {
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)) {
+ if (lp.mViewType != LayoutParams.CUSTOM || !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);
+ width += measureChildCollapseMargins(child, widthMeasureSpec, width,
+ heightMeasureSpec, 0, collapsingMargins);
height = Math.max(height, child.getMeasuredHeight() + getVerticalMargins(child));
childState = combineMeasuredStates(childState, child.getMeasuredState());
}
@@ -611,27 +1108,51 @@ public class Toolbar extends ViewGroup {
int left = paddingLeft;
int right = width - paddingRight;
+ final int[] collapsingMargins = mTempMargins;
+ collapsingMargins[0] = collapsingMargins[1] = 0;
+
if (shouldLayout(mNavButtonView)) {
if (isRtl) {
- right = layoutChildRight(mNavButtonView, right);
+ right = layoutChildRight(mNavButtonView, right, collapsingMargins);
} else {
- left = layoutChildLeft(mNavButtonView, left);
+ left = layoutChildLeft(mNavButtonView, left, collapsingMargins);
+ }
+ }
+
+ if (shouldLayout(mCollapseButtonView)) {
+ if (isRtl) {
+ right = layoutChildRight(mCollapseButtonView, right, collapsingMargins);
+ } else {
+ left = layoutChildLeft(mCollapseButtonView, left, collapsingMargins);
}
}
if (shouldLayout(mMenuView)) {
if (isRtl) {
- left = layoutChildLeft(mMenuView, left);
+ left = layoutChildLeft(mMenuView, left, collapsingMargins);
} else {
- right = layoutChildRight(mMenuView, right);
+ right = layoutChildRight(mMenuView, right, collapsingMargins);
+ }
+ }
+
+ collapsingMargins[0] = Math.max(0, getContentInsetLeft() - left);
+ collapsingMargins[1] = Math.max(0, getContentInsetRight() - (width - paddingRight - right));
+ left = Math.max(left, getContentInsetLeft());
+ right = Math.min(right, width - paddingRight - getContentInsetRight());
+
+ if (shouldLayout(mExpandedActionView)) {
+ if (isRtl) {
+ right = layoutChildRight(mExpandedActionView, right, collapsingMargins);
+ } else {
+ left = layoutChildLeft(mExpandedActionView, left, collapsingMargins);
}
}
if (shouldLayout(mLogoView)) {
if (isRtl) {
- right = layoutChildRight(mLogoView, right);
+ right = layoutChildRight(mLogoView, right, collapsingMargins);
} else {
- left = layoutChildLeft(mLogoView, left);
+ left = layoutChildLeft(mLogoView, left, collapsingMargins);
}
}
@@ -649,79 +1170,83 @@ public class Toolbar extends ViewGroup {
if (layoutTitle || layoutSubtitle) {
int titleTop;
+ final View topChild = layoutTitle ? mTitleTextView : mSubtitleTextView;
+ final View bottomChild = layoutSubtitle ? mSubtitleTextView : mTitleTextView;
+ final LayoutParams toplp = (LayoutParams) topChild.getLayoutParams();
+ final LayoutParams bottomlp = (LayoutParams) bottomChild.getLayoutParams();
+
switch (mGravity & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.TOP:
- titleTop = getPaddingTop();
+ titleTop = getPaddingTop() + toplp.topMargin + mTitleMarginTop;
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;
+ if (spaceAbove < toplp.topMargin + mTitleMarginTop) {
+ spaceAbove = toplp.topMargin + mTitleMarginTop;
} else {
final int spaceBelow = height - paddingBottom - titleHeight -
spaceAbove - paddingTop;
- if (spaceBelow < lp.bottomMargin + mTitleMarginBottom) {
+ if (spaceBelow < toplp.bottomMargin + mTitleMarginBottom) {
spaceAbove = Math.max(0, spaceAbove -
- (lp.bottomMargin + mTitleMarginBottom - spaceBelow));
+ (bottomlp.bottomMargin + mTitleMarginBottom - spaceBelow));
}
}
titleTop = paddingTop + spaceAbove;
break;
case Gravity.BOTTOM:
- titleTop = height - paddingBottom - titleHeight;
+ titleTop = height - paddingBottom - bottomlp.bottomMargin - mTitleMarginBottom -
+ titleHeight;
break;
}
if (isRtl) {
+ final int rd = mTitleMarginStart - collapsingMargins[1];
+ right -= Math.max(0, rd);
+ collapsingMargins[1] = Math.max(0, -rd);
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;
+ titleRight = titleLeft - 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;
+ subtitleRight = subtitleRight - mTitleMarginEnd;
titleTop = subtitleBottom + lp.bottomMargin;
}
right = Math.max(titleRight, subtitleRight);
} else {
+ final int ld = mTitleMarginStart - collapsingMargins[0];
+ left += Math.max(0, ld);
+ collapsingMargins[0] = Math.max(0, -ld);
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;
+ titleLeft = titleRight + 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;
+ subtitleLeft = subtitleRight + mTitleMarginEnd;
titleTop = subtitleBottom + lp.bottomMargin;
}
left = Math.max(titleLeft, subtitleLeft);
@@ -734,19 +1259,19 @@ public class Toolbar extends ViewGroup {
addCustomViewsWithGravity(mTempViews, Gravity.LEFT);
final int leftViewsCount = mTempViews.size();
for (int i = 0; i < leftViewsCount; i++) {
- left = layoutChildLeft(getChildAt(i), left);
+ left = layoutChildLeft(mTempViews.get(i), left, collapsingMargins);
}
addCustomViewsWithGravity(mTempViews, Gravity.RIGHT);
final int rightViewsCount = mTempViews.size();
for (int i = 0; i < rightViewsCount; i++) {
- right = layoutChildRight(getChildAt(i), right);
+ right = layoutChildRight(mTempViews.get(i), right, collapsingMargins);
}
// 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);
+ addCustomViewsWithGravity(mTempViews, Gravity.CENTER_HORIZONTAL);
+ final int centerViewsWidth = getViewListMeasuredWidth(mTempViews, collapsingMargins);
final int parentCenter = paddingLeft + (width - paddingLeft - paddingRight) / 2;
final int halfCenterViewsWidth = centerViewsWidth / 2;
int centerLeft = parentCenter - halfCenterViewsWidth;
@@ -759,37 +1284,51 @@ public class Toolbar extends ViewGroup {
final int centerViewsCount = mTempViews.size();
for (int i = 0; i < centerViewsCount; i++) {
- centerLeft = layoutChildLeft(getChildAt(i), centerLeft);
+ centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft, collapsingMargins);
}
mTempViews.clear();
}
- private int getViewListMeasuredWidth(List<View> views) {
+ private int getViewListMeasuredWidth(List<View> views, int[] collapsingMargins) {
+ int collapseLeft = collapsingMargins[0];
+ int collapseRight = collapsingMargins[1];
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;
+ final int l = lp.leftMargin - collapseLeft;
+ final int r = lp.rightMargin - collapseRight;
+ final int leftMargin = Math.max(0, l);
+ final int rightMargin = Math.max(0, r);
+ collapseLeft = Math.max(0, -l);
+ collapseRight = Math.max(0, -r);
+ width += leftMargin + v.getMeasuredWidth() + rightMargin;
}
return width;
}
- private int layoutChildLeft(View child, int left) {
+ private int layoutChildLeft(View child, int left, int[] collapsingMargins) {
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;
+ final int l = lp.leftMargin - collapsingMargins[0];
+ left += Math.max(0, l);
+ collapsingMargins[0] = Math.max(0, -l);
+ final int top = getChildTop(child);
+ final int childWidth = child.getMeasuredWidth();
+ child.layout(left, top, left + childWidth, top + child.getMeasuredHeight());
+ left += childWidth + lp.rightMargin;
return left;
}
- private int layoutChildRight(View child, int right) {
+ private int layoutChildRight(View child, int right, int[] collapsingMargins) {
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;
+ final int r = lp.rightMargin - collapsingMargins[1];
+ right -= Math.max(0, r);
+ collapsingMargins[1] = Math.max(0, -r);
+ final int top = getChildTop(child);
+ final int childWidth = child.getMeasuredWidth();
+ child.layout(right - childWidth, top, right, top + child.getMeasuredHeight());
+ right -= childWidth + lp.leftMargin;
return right;
}
@@ -853,17 +1392,16 @@ public class Toolbar extends ViewGroup {
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) &&
+ if (lp.mViewType == LayoutParams.CUSTOM && 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) &&
+ if (lp.mViewType == LayoutParams.CUSTOM && shouldLayout(child) &&
getChildHorizontalGravity(lp.gravity) == absGrav) {
views.add(child);
}
@@ -900,14 +1438,16 @@ public class Toolbar extends ViewGroup {
}
@Override
- public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
- return super.generateLayoutParams(attrs);
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
}
@Override
- protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
if (p instanceof LayoutParams) {
return new LayoutParams((LayoutParams) p);
+ } else if (p instanceof ActionBar.LayoutParams) {
+ return new LayoutParams((ActionBar.LayoutParams) p);
} else if (p instanceof MarginLayoutParams) {
return new LayoutParams((MarginLayoutParams) p);
} else {
@@ -916,7 +1456,7 @@ public class Toolbar extends ViewGroup {
}
@Override
- protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+ protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@@ -929,6 +1469,25 @@ public class Toolbar extends ViewGroup {
return ((LayoutParams) child.getLayoutParams()).mViewType == LayoutParams.CUSTOM;
}
+ /** @hide */
+ public DecorToolbar getWrapper() {
+ if (mWrapper == null) {
+ mWrapper = new ToolbarWidgetWrapper(this);
+ }
+ return mWrapper;
+ }
+
+ private void setChildVisibilityForExpandedActionView(boolean expand) {
+ 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.EXPANDED && child != mMenuView) {
+ child.setVisibility(expand ? GONE : VISIBLE);
+ }
+ }
+ }
+
/**
* Interface responsible for receiving menu item click events if the items themselves
* do not have individual item click listeners.
@@ -949,44 +1508,15 @@ public class Toolbar extends ViewGroup {
*
* @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;
-
+ public static class LayoutParams extends ActionBar.LayoutParams {
static final int CUSTOM = 0;
static final int SYSTEM = 1;
+ static final int EXPANDED = 2;
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) {
@@ -1006,7 +1536,11 @@ public class Toolbar extends ViewGroup {
public LayoutParams(LayoutParams source) {
super(source);
- this.gravity = source.gravity;
+ mViewType = source.mViewType;
+ }
+
+ public LayoutParams(ActionBar.LayoutParams source) {
+ super(source);
}
public LayoutParams(MarginLayoutParams source) {
@@ -1045,4 +1579,126 @@ public class Toolbar extends ViewGroup {
}
};
}
+
+ private class ExpandedActionViewMenuPresenter implements MenuPresenter {
+ MenuBuilder mMenu;
+ MenuItemImpl mCurrentExpandedItem;
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ // Clear the expanded action view when menus change.
+ if (mMenu != null && mCurrentExpandedItem != null) {
+ mMenu.collapseItemActionView(mCurrentExpandedItem);
+ }
+ mMenu = menu;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ return null;
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ // Make sure the expanded item we have is still there.
+ if (mCurrentExpandedItem != null) {
+ boolean found = false;
+
+ if (mMenu != null) {
+ final int count = mMenu.size();
+ for (int i = 0; i < count; i++) {
+ final MenuItem item = mMenu.getItem(i);
+ if (item == mCurrentExpandedItem) {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ // The item we had expanded disappeared. Collapse.
+ collapseItemActionView(mMenu, mCurrentExpandedItem);
+ }
+ }
+ }
+
+ @Override
+ public void setCallback(Callback cb) {
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ }
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ @Override
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ ensureCollapseButtonView();
+ if (mCollapseButtonView.getParent() != Toolbar.this) {
+ addView(mCollapseButtonView);
+ }
+ mExpandedActionView = item.getActionView();
+ mCurrentExpandedItem = item;
+ if (mExpandedActionView.getParent() != Toolbar.this) {
+ final LayoutParams lp = generateDefaultLayoutParams();
+ lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
+ lp.mViewType = LayoutParams.EXPANDED;
+ mExpandedActionView.setLayoutParams(lp);
+ addView(mExpandedActionView);
+ }
+
+ setChildVisibilityForExpandedActionView(true);
+ requestLayout();
+ item.setActionViewExpanded(true);
+
+ if (mExpandedActionView instanceof CollapsibleActionView) {
+ ((CollapsibleActionView) mExpandedActionView).onActionViewExpanded();
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ // Do this before detaching the actionview from the hierarchy, in case
+ // it needs to dismiss the soft keyboard, etc.
+ if (mExpandedActionView instanceof CollapsibleActionView) {
+ ((CollapsibleActionView) mExpandedActionView).onActionViewCollapsed();
+ }
+
+ removeView(mExpandedActionView);
+ removeView(mCollapseButtonView);
+ mExpandedActionView = null;
+
+ setChildVisibilityForExpandedActionView(false);
+ mCurrentExpandedItem = null;
+ requestLayout();
+ item.setActionViewExpanded(false);
+
+ return true;
+ }
+
+ @Override
+ public int getId() {
+ return 0;
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ return null;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ }
+ }
}
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index f23c64f..2b62552 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -30,6 +30,7 @@ import android.media.MediaPlayer.OnInfoListener;
import android.media.Metadata;
import android.media.SubtitleController;
import android.media.SubtitleTrack.RenderingWidget;
+import android.media.TtmlRenderer;
import android.media.WebVttRenderer;
import android.net.Uri;
import android.os.Looper;
@@ -314,6 +315,7 @@ public class VideoView extends SurfaceView
final SubtitleController controller = new SubtitleController(
context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);
controller.registerRenderer(new WebVttRenderer(context));
+ controller.registerRenderer(new TtmlRenderer(context));
mMediaPlayer.setSubtitleAnchor(controller, this);
if (mAudioSession != 0) {
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index 4726da7..5547a10 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -26,6 +26,7 @@ import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
@@ -38,6 +39,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
@@ -121,11 +123,14 @@ public class AlertController {
private int mCheckedItem = -1;
private int mAlertDialogLayout;
+ private int mButtonPanelSideLayout;
private int mListLayout;
private int mMultiChoiceItemLayout;
private int mSingleChoiceItemLayout;
private int mListItemLayout;
+ private int mButtonPanelLayoutHint = AlertDialog.LAYOUT_HINT_NONE;
+
private Handler mHandler;
private final View.OnClickListener mButtonHandler = new View.OnClickListener() {
@@ -197,6 +202,9 @@ public class AlertController {
mAlertDialogLayout = a.getResourceId(com.android.internal.R.styleable.AlertDialog_layout,
com.android.internal.R.layout.alert_dialog);
+ mButtonPanelSideLayout = a.getResourceId(
+ com.android.internal.R.styleable.AlertDialog_buttonPanelSideLayout, 0);
+
mListLayout = a.getResourceId(
com.android.internal.R.styleable.AlertDialog_listLayout,
com.android.internal.R.layout.select_dialog);
@@ -238,8 +246,21 @@ public class AlertController {
public void installContent() {
/* We use a custom title so never request a window title */
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
- mWindow.setContentView(mAlertDialogLayout);
+ int contentView = selectContentView();
+ mWindow.setContentView(contentView);
setupView();
+ setupDecor();
+ }
+
+ private int selectContentView() {
+ if (mButtonPanelSideLayout == 0) {
+ return mAlertDialogLayout;
+ }
+ if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
+ return mButtonPanelSideLayout;
+ }
+ // TODO: use layout hint side for long messages/lists
+ return mAlertDialogLayout;
}
public void setTitle(CharSequence title) {
@@ -296,6 +317,13 @@ public class AlertController {
}
/**
+ * Sets a hint for the best button panel layout.
+ */
+ public void setButtonPanelLayoutHint(int layoutHint) {
+ mButtonPanelLayoutHint = layoutHint;
+ }
+
+ /**
* Sets a click listener or a message to be sent when the button is clicked.
* You only need to pass one of {@code listener} or {@code msg}.
*
@@ -415,7 +443,28 @@ public class AlertController {
public boolean onKeyUp(int keyCode, KeyEvent event) {
return mScrollView != null && mScrollView.executeKeyEvent(event);
}
-
+
+ private void setupDecor() {
+ final View decor = mWindow.getDecorView();
+ final View parent = mWindow.findViewById(R.id.parentPanel);
+ if (parent != null && decor != null) {
+ decor.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
+ @Override
+ public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) {
+ if (insets.isRound()) {
+ // TODO: Get the padding as a function of the window size.
+ int roundOffset = mContext.getResources().getDimensionPixelOffset(
+ R.dimen.alert_dialog_round_padding);
+ parent.setPadding(roundOffset, roundOffset, roundOffset, roundOffset);
+ }
+ return insets.consumeSystemWindowInsets();
+ }
+ });
+ decor.setFitsSystemWindows(true);
+ decor.requestApplyInsets();
+ }
+ }
+
private void setupView() {
LinearLayout contentPanel = (LinearLayout) mWindow.findViewById(R.id.contentPanel);
setupContent(contentPanel);
@@ -636,14 +685,31 @@ public class AlertController {
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);
- 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);
- final int centerDark = a.getResourceId(
- R.styleable.AlertDialog_centerDark, R.drawable.popup_center_dark);
+ int fullDark = 0;
+ int topDark = 0;
+ int centerDark = 0;
+ int bottomDark = 0;
+ int fullBright = 0;
+ int topBright = 0;
+ int centerBright = 0;
+ int bottomBright = 0;
+ int bottomMedium = 0;
+ if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.KITKAT) {
+ fullDark = R.drawable.popup_full_dark;
+ topDark = R.drawable.popup_top_dark;
+ centerDark = R.drawable.popup_center_dark;
+ bottomDark = R.drawable.popup_bottom_dark;
+ fullBright = R.drawable.popup_full_bright;
+ topBright = R.drawable.popup_top_bright;
+ centerBright = R.drawable.popup_center_bright;
+ bottomBright = R.drawable.popup_bottom_bright;
+ bottomMedium = R.drawable.popup_bottom_medium;
+ }
+ topBright = a.getResourceId(R.styleable.AlertDialog_topBright, topBright);
+ topDark = a.getResourceId(R.styleable.AlertDialog_topDark, topDark);
+ centerBright = a.getResourceId(R.styleable.AlertDialog_centerBright, centerBright);
+ centerDark = a.getResourceId(R.styleable.AlertDialog_centerDark, centerDark);
+
/* We now set the background of all of the sections of the alert.
* First collect together each section that is being displayed along
@@ -707,22 +773,17 @@ public class AlertController {
if (lastView != null) {
if (setView) {
- 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);
+ bottomBright = a.getResourceId(R.styleable.AlertDialog_bottomBright, bottomBright);
+ bottomMedium = a.getResourceId(R.styleable.AlertDialog_bottomMedium, bottomMedium);
+ bottomDark = a.getResourceId(R.styleable.AlertDialog_bottomDark, bottomDark);
// 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);
+ fullBright = a.getResourceId(R.styleable.AlertDialog_fullBright, fullBright);
+ fullDark = a.getResourceId(R.styleable.AlertDialog_fullDark, fullDark);
lastView.setBackgroundResource(lastLight ? fullBright : fullDark);
}
@@ -965,7 +1026,7 @@ public class AlertController {
? dialog.mSingleChoiceItemLayout : dialog.mListItemLayout;
if (mCursor == null) {
adapter = (mAdapter != null) ? mAdapter
- : new ArrayAdapter<CharSequence>(mContext, layout, R.id.text1, mItems);
+ : new CheckedItemAdapter(mContext, layout, R.id.text1, mItems);
} else {
adapter = new SimpleCursorAdapter(mContext, layout,
mCursor, new String[]{mLabelColumn}, new int[]{R.id.text1});
@@ -1020,4 +1081,20 @@ public class AlertController {
}
}
+ private static class CheckedItemAdapter extends ArrayAdapter<CharSequence> {
+ public CheckedItemAdapter(Context context, int resource, int textViewResourceId,
+ CharSequence[] objects) {
+ super(context, resource, textViewResourceId, objects);
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+ }
}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index cd75010..8e6fa58 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -17,6 +17,7 @@
package com.android.internal.app;
import android.app.AppOpsManager;
+import android.os.Bundle;
import com.android.internal.app.IAppOpsCallback;
interface IAppOpsService {
@@ -38,4 +39,10 @@ interface IAppOpsService {
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);
+
+ void setDeviceOwner(String packageName);
+ void setProfileOwner(String packageName, int userHandle);
+ void setUserRestrictions(in Bundle restrictions, int userHandle);
+ void removeUser(int userHandle);
+
}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 04547495..0769b08 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -46,14 +46,15 @@ interface IBatteryStats {
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 noteStopWakelock(int uid, int pid, String name, String historyName, 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,
+ void noteChangeWakelockFromSource(in WorkSource ws, int pid, String name, String histyoryName,
+ 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 noteStopWakelockFromSource(in WorkSource ws, int pid, String name, String historyName,
+ int type);
void noteVibratorOn(int uid, long durationMillis);
void noteVibratorOff(int uid);
diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl
index 03d3b22..77f0dec 100644
--- a/core/java/com/android/internal/app/IMediaContainerService.aidl
+++ b/core/java/com/android/internal/app/IMediaContainerService.aidl
@@ -25,16 +25,18 @@ import android.content.res.ObbInfo;
interface IMediaContainerService {
String copyResourceToContainer(in Uri packageURI, String containerId, String key,
String resFileName, String publicResFileName, boolean isExternal,
- boolean isForwardLocked);
+ boolean isForwardLocked, in String abiOverride);
int copyResource(in Uri packageURI, in ContainerEncryptionParams encryptionParams,
in ParcelFileDescriptor outStream);
- PackageInfoLite getMinimalPackageInfo(in String packagePath, in int flags, in long threshold);
+ PackageInfoLite getMinimalPackageInfo(in String packagePath, in int flags, in long threshold,
+ in String abiOverride);
boolean checkInternalFreeStorage(in Uri fileUri, boolean isForwardLocked, in long threshold);
- boolean checkExternalFreeStorage(in Uri fileUri, boolean isForwardLocked);
+ boolean checkExternalFreeStorage(in Uri fileUri, boolean isForwardLocked, in String abiOverride);
ObbInfo getObbInfo(in String filename);
long calculateDirectorySize(in String directory);
/** Return file system stats: [0] is total bytes, [1] is available bytes */
long[] getFileSystemStats(in String path);
void clearDirectory(in String directory);
- long calculateInstalledSize(in String packagePath, boolean isForwardLocked);
+ long calculateInstalledSize(in String packagePath, boolean isForwardLocked,
+ in String abiOverride);
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl
index 737906a..2900595 100644
--- a/core/java/com/android/internal/app/IVoiceInteractor.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl
@@ -26,7 +26,9 @@ import com.android.internal.app.IVoiceInteractorRequest;
*/
interface IVoiceInteractor {
IVoiceInteractorRequest startConfirmation(String callingPackage,
- IVoiceInteractorCallback callback, String prompt, in Bundle extras);
+ IVoiceInteractorCallback callback, CharSequence prompt, in Bundle extras);
+ IVoiceInteractorRequest startAbortVoice(String callingPackage,
+ IVoiceInteractorCallback callback, CharSequence message, 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
index c6f93e1..8dbf9d4 100644
--- a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
@@ -26,6 +26,7 @@ import com.android.internal.app.IVoiceInteractorRequest;
oneway interface IVoiceInteractorCallback {
void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
in Bundle result);
+ void deliverAbortVoiceResult(IVoiceInteractorRequest request, 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/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 2f74372..01e5d40 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -35,8 +35,8 @@ 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.
+ * This is used in conjunction with the {@link setCrossProfileIntentFilter} method of
+ * {@link DevicePolicyManager} to enable intents to be passed in and out of a managed profile.
*/
public class IntentForwarderActivity extends Activity {
@@ -84,6 +84,7 @@ public class IntentForwarderActivity extends Activity {
Slog.e(TAG, "PackageManagerService is dead?");
}
if (canForward) {
+ newIntent.prepareToLeaveUser(callingUserId);
startActivityAsUser(newIntent, userDest);
} else {
Slog.wtf(TAG, "the intent: " + newIntent + "cannot be forwarded from user "
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 882bec9..7e11850 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -1100,7 +1100,7 @@ public final class ProcessStats implements Parcelable {
public boolean evaluateSystemProperties(boolean update) {
boolean changed = false;
- String runtime = SystemProperties.get("persist.sys.dalvik.vm.lib.1",
+ String runtime = SystemProperties.get("persist.sys.dalvik.vm.lib.2",
VMRuntime.getRuntime().vmLibrary());
if (!Objects.equals(runtime, mRuntime)) {
changed = true;
@@ -1108,13 +1108,6 @@ public final class ProcessStats implements Parcelable {
mRuntime = runtime;
}
}
- String webview = WebViewFactory.useExperimentalWebView() ? "chromeview" : "webview";
- if (!Objects.equals(webview, mWebView)) {
- changed = true;
- if (update) {
- mWebView = webview;
- }
- }
return changed;
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 591267e..183dd05 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -484,8 +484,7 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
mList.clear();
if (mBaseResolveList != null) {
- currentResolveList = mBaseResolveList;
- mOrigResolveList = null;
+ currentResolveList = mOrigResolveList = mBaseResolveList;
} else {
currentResolveList = mOrigResolveList = mPm.queryIntentActivities(
mIntent, PackageManager.MATCH_DEFAULT_ONLY
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index afb6f7c..6056bf2 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -23,37 +23,50 @@ import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.view.ActionMode;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
+import android.view.Window;
import android.widget.SpinnerAdapter;
import android.widget.Toolbar;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.widget.DecorToolbar;
+import com.android.internal.widget.ToolbarWidgetWrapper;
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 DecorToolbar mDecorToolbar;
+ private Window.Callback mWindowCallback;
private boolean mLastMenuVisibility;
private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners =
new ArrayList<OnMenuVisibilityListener>();
- public ToolbarActionBar(Toolbar toolbar) {
+ private final Runnable mMenuInvalidator = new Runnable() {
+ @Override
+ public void run() {
+ populateOptionsMenu();
+ }
+ };
+
+ private final Toolbar.OnMenuItemClickListener mMenuClicker =
+ new Toolbar.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ return mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
+ }
+ };
+
+ public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback) {
mToolbar = toolbar;
+ mDecorToolbar = new ToolbarWidgetWrapper(toolbar);
+ mWindowCallback = windowCallback;
+ toolbar.setOnMenuItemClickListener(mMenuClicker);
+ mDecorToolbar.setWindowTitle(title);
}
@Override
@@ -63,19 +76,8 @@ public class ToolbarActionBar extends ActionBar {
@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;
+ view.setLayoutParams(layoutParams);
+ mDecorToolbar.setCustomView(view);
}
@Override
@@ -86,48 +88,22 @@ public class ToolbarActionBar extends ActionBar {
@Override
public void setIcon(int resId) {
- mIconResId = resId;
- mIconDrawable = null;
- updateToolbarLogo();
+ mDecorToolbar.setIcon(resId);
}
@Override
public void setIcon(Drawable icon) {
- mIconResId = 0;
- mIconDrawable = icon;
- updateToolbarLogo();
+ mDecorToolbar.setIcon(icon);
}
@Override
public void setLogo(int resId) {
- mLogoResId = resId;
- mLogoDrawable = null;
- updateToolbarLogo();
+ mDecorToolbar.setLogo(resId);
}
@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);
+ mDecorToolbar.setLogo(logo);
}
@Override
@@ -219,42 +195,22 @@ public class ToolbarActionBar extends ActionBar {
@Override
public void setTitle(CharSequence title) {
- mTitle = title;
- mTitleResId = 0;
- updateToolbarTitle();
+ mDecorToolbar.setTitle(title);
}
@Override
public void setTitle(int resId) {
- mTitleResId = resId;
- mTitle = null;
- updateToolbarTitle();
+ mDecorToolbar.setTitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
}
@Override
public void setSubtitle(CharSequence subtitle) {
- mSubtitle = subtitle;
- mSubtitleResId = 0;
- updateToolbarTitle();
+ mDecorToolbar.setSubtitle(subtitle);
}
@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);
+ mDecorToolbar.setSubtitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
}
@Override
@@ -264,9 +220,8 @@ public class ToolbarActionBar extends ActionBar {
@Override
public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) {
- final int oldOptions = mDisplayOptions;
- mDisplayOptions = (options & mask) | (mDisplayOptions & ~mask);
- final int optionsChanged = oldOptions ^ mDisplayOptions;
+ mDecorToolbar.setDisplayOptions((options & mask) |
+ mDecorToolbar.getDisplayOptions() & ~mask);
}
@Override
@@ -301,7 +256,7 @@ public class ToolbarActionBar extends ActionBar {
@Override
public View getCustomView() {
- return mCustomView;
+ return mDecorToolbar.getCustomView();
}
@Override
@@ -327,7 +282,7 @@ public class ToolbarActionBar extends ActionBar {
@Override
public int getDisplayOptions() {
- return mDisplayOptions;
+ return mDecorToolbar.getDisplayOptions();
}
@Override
@@ -425,6 +380,54 @@ public class ToolbarActionBar extends ActionBar {
return mToolbar.getVisibility() == View.VISIBLE;
}
+ @Override
+ public boolean openOptionsMenu() {
+ return mToolbar.showOverflowMenu();
+ }
+
+ @Override
+ public boolean invalidateOptionsMenu() {
+ mToolbar.removeCallbacks(mMenuInvalidator);
+ mToolbar.postOnAnimation(mMenuInvalidator);
+ return true;
+ }
+
+ @Override
+ public boolean collapseActionView() {
+ if (mToolbar.hasExpandedActionView()) {
+ mToolbar.collapseActionView();
+ return true;
+ }
+ return false;
+ }
+
+ void populateOptionsMenu() {
+ final Menu menu = mToolbar.getMenu();
+ final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null;
+ if (mb != null) {
+ mb.stopDispatchingItemsChanged();
+ }
+ try {
+ menu.clear();
+ if (!mWindowCallback.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu) ||
+ !mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) {
+ menu.clear();
+ }
+ } finally {
+ if (mb != null) {
+ mb.startDispatchingItemsChanged();
+ }
+ }
+ }
+
+ @Override
+ public boolean onMenuKeyEvent(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ openOptionsMenu();
+ }
+ return true;
+ }
+
public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
mMenuVisibilityListeners.add(listener);
}
diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index 66548f0..c0b5b97 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -18,7 +18,10 @@ package com.android.internal.app;
import android.animation.ValueAnimator;
import android.content.res.TypedArray;
+import android.view.ViewGroup;
import android.view.ViewParent;
+import android.widget.AdapterView;
+import android.widget.Toolbar;
import com.android.internal.R;
import com.android.internal.view.ActionBarPolicy;
import com.android.internal.view.menu.MenuBuilder;
@@ -28,6 +31,7 @@ import com.android.internal.widget.ActionBarContainer;
import com.android.internal.widget.ActionBarContextView;
import com.android.internal.widget.ActionBarOverlayLayout;
import com.android.internal.widget.ActionBarView;
+import com.android.internal.widget.DecorToolbar;
import com.android.internal.widget.ScrollingTabContainerView;
import android.animation.Animator;
@@ -55,6 +59,7 @@ import android.view.Window;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AnimationUtils;
import android.widget.SpinnerAdapter;
+import com.android.internal.widget.ToolbarWidgetWrapper;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -77,7 +82,7 @@ public class WindowDecorActionBar extends ActionBar implements
private ActionBarOverlayLayout mOverlayLayout;
private ActionBarContainer mContainerView;
- private ActionBarView mActionView;
+ private DecorToolbar mDecorToolbar;
private ActionBarContextView mContextView;
private ActionBarContainer mSplitView;
private View mContentView;
@@ -183,11 +188,11 @@ public class WindowDecorActionBar extends ActionBar implements
private void init(View decor) {
mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(
- com.android.internal.R.id.action_bar_overlay_layout);
+ com.android.internal.R.id.decor_content_parent);
if (mOverlayLayout != null) {
mOverlayLayout.setActionBarVisibilityCallback(this);
}
- mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar);
+ mDecorToolbar = getDecorToolbar(decor.findViewById(com.android.internal.R.id.action_bar));
mContextView = (ActionBarContextView) decor.findViewById(
com.android.internal.R.id.action_context_bar);
mContainerView = (ActionBarContainer) decor.findViewById(
@@ -195,18 +200,17 @@ public class WindowDecorActionBar extends ActionBar implements
mSplitView = (ActionBarContainer) decor.findViewById(
com.android.internal.R.id.split_action_bar);
- if (mActionView == null || mContextView == null || mContainerView == null) {
+ if (mDecorToolbar == null || mContextView == null || mContainerView == null) {
throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
"with a compatible window decor layout");
}
- mContext = mActionView.getContext();
- mActionView.setContextView(mContextView);
- mContextDisplayMode = mActionView.isSplitActionBar() ?
+ mContext = mDecorToolbar.getContext();
+ mContextDisplayMode = mDecorToolbar.isSplit() ?
CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL;
// This was initially read from the action bar style
- final int current = mActionView.getDisplayOptions();
+ final int current = mDecorToolbar.getDisplayOptions();
final boolean homeAsUp = (current & DISPLAY_HOME_AS_UP) != 0;
if (homeAsUp) {
mDisplayHomeAsUpSet = true;
@@ -225,6 +229,17 @@ public class WindowDecorActionBar extends ActionBar implements
a.recycle();
}
+ private DecorToolbar getDecorToolbar(View view) {
+ if (view instanceof DecorToolbar) {
+ return (DecorToolbar) view;
+ } else if (view instanceof Toolbar) {
+ return ((Toolbar) view).getWrapper();
+ } else {
+ throw new IllegalStateException("Can't make a decor toolbar out of " +
+ view.getClass().getSimpleName());
+ }
+ }
+
public void onConfigurationChanged(Configuration newConfig) {
setHasEmbeddedTabs(ActionBarPolicy.get(mContext).hasEmbeddedTabs());
}
@@ -233,11 +248,11 @@ public class WindowDecorActionBar extends ActionBar implements
mHasEmbeddedTabs = hasEmbeddedTabs;
// Switch tab layout configuration if needed
if (!mHasEmbeddedTabs) {
- mActionView.setEmbeddedTabView(null);
+ mDecorToolbar.setEmbeddedTabView(null);
mContainerView.setTabContainer(mTabScrollView);
} else {
mContainerView.setTabContainer(null);
- mActionView.setEmbeddedTabView(mTabScrollView);
+ mDecorToolbar.setEmbeddedTabView(mTabScrollView);
}
final boolean isInTabMode = getNavigationMode() == NAVIGATION_MODE_TABS;
if (mTabScrollView != null) {
@@ -250,7 +265,7 @@ public class WindowDecorActionBar extends ActionBar implements
mTabScrollView.setVisibility(View.GONE);
}
}
- mActionView.setCollapsable(!mHasEmbeddedTabs && isInTabMode);
+ mDecorToolbar.setCollapsible(!mHasEmbeddedTabs && isInTabMode);
mOverlayLayout.setHasNonEmbeddedTabs(!mHasEmbeddedTabs && isInTabMode);
}
@@ -263,12 +278,12 @@ public class WindowDecorActionBar extends ActionBar implements
if (mHasEmbeddedTabs) {
tabScroller.setVisibility(View.VISIBLE);
- mActionView.setEmbeddedTabView(tabScroller);
+ mDecorToolbar.setEmbeddedTabView(tabScroller);
} else {
if (getNavigationMode() == NAVIGATION_MODE_TABS) {
tabScroller.setVisibility(View.VISIBLE);
if (mOverlayLayout != null) {
- mOverlayLayout.requestFitSystemWindows();
+ mOverlayLayout.requestApplyInsets();
}
} else {
tabScroller.setVisibility(View.GONE);
@@ -326,7 +341,8 @@ public class WindowDecorActionBar extends ActionBar implements
@Override
public void setCustomView(int resId) {
- setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId, mActionView, false));
+ setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId,
+ mDecorToolbar.getViewGroup(), false));
}
@Override
@@ -356,7 +372,7 @@ public class WindowDecorActionBar extends ActionBar implements
@Override
public void setHomeButtonEnabled(boolean enable) {
- mActionView.setHomeButtonEnabled(enable);
+ mDecorToolbar.setHomeButtonEnabled(enable);
}
@Override
@@ -370,12 +386,12 @@ public class WindowDecorActionBar extends ActionBar implements
}
public void setSelectedNavigationItem(int position) {
- switch (mActionView.getNavigationMode()) {
+ switch (mDecorToolbar.getNavigationMode()) {
case NAVIGATION_MODE_TABS:
selectTab(mTabs.get(position));
break;
case NAVIGATION_MODE_LIST:
- mActionView.setDropdownSelectedPosition(position);
+ mDecorToolbar.setDropdownSelectedPosition(position);
break;
default:
throw new IllegalStateException(
@@ -399,26 +415,26 @@ public class WindowDecorActionBar extends ActionBar implements
}
public void setTitle(CharSequence title) {
- mActionView.setTitle(title);
+ mDecorToolbar.setTitle(title);
}
public void setSubtitle(CharSequence subtitle) {
- mActionView.setSubtitle(subtitle);
+ mDecorToolbar.setSubtitle(subtitle);
}
public void setDisplayOptions(int options) {
if ((options & DISPLAY_HOME_AS_UP) != 0) {
mDisplayHomeAsUpSet = true;
}
- mActionView.setDisplayOptions(options);
+ mDecorToolbar.setDisplayOptions(options);
}
public void setDisplayOptions(int options, int mask) {
- final int current = mActionView.getDisplayOptions();
+ final int current = mDecorToolbar.getDisplayOptions();
if ((mask & DISPLAY_HOME_AS_UP) != 0) {
mDisplayHomeAsUpSet = true;
}
- mActionView.setDisplayOptions((options & mask) | (current & ~mask));
+ mDecorToolbar.setDisplayOptions((options & mask) | (current & ~mask));
}
public void setBackgroundDrawable(Drawable d) {
@@ -436,23 +452,23 @@ public class WindowDecorActionBar extends ActionBar implements
}
public View getCustomView() {
- return mActionView.getCustomNavigationView();
+ return mDecorToolbar.getCustomView();
}
public CharSequence getTitle() {
- return mActionView.getTitle();
+ return mDecorToolbar.getTitle();
}
public CharSequence getSubtitle() {
- return mActionView.getSubtitle();
+ return mDecorToolbar.getSubtitle();
}
public int getNavigationMode() {
- return mActionView.getNavigationMode();
+ return mDecorToolbar.getNavigationMode();
}
public int getDisplayOptions() {
- return mActionView.getDisplayOptions();
+ return mDecorToolbar.getDisplayOptions();
}
public ActionMode startActionMode(ActionMode.Callback callback) {
@@ -572,7 +588,7 @@ public class WindowDecorActionBar extends ActionBar implements
return;
}
- final FragmentTransaction trans = mActionView.isInEditMode() ? null :
+ final FragmentTransaction trans = mDecorToolbar.getViewGroup().isInEditMode() ? null :
mActivity.getFragmentManager().beginTransaction().disallowAddToBackStack();
if (mSelectedTab == tab) {
@@ -828,13 +844,18 @@ public class WindowDecorActionBar extends ActionBar implements
hideForActionMode();
}
- mActionView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE);
+ mDecorToolbar.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE);
mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE);
- if (mTabScrollView != null && !mActionView.hasEmbeddedTabs() && mActionView.isCollapsed()) {
+ if (mTabScrollView != null && !mDecorToolbar.hasEmbeddedTabs() &&
+ isCollapsed(mDecorToolbar.getViewGroup())) {
mTabScrollView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE);
}
}
+ private boolean isCollapsed(View view) {
+ return view == null || view.getVisibility() == View.GONE || view.getMeasuredHeight() == 0;
+ }
+
public Context getThemedContext() {
if (mThemedContext == null) {
TypedValue outValue = new TypedValue();
@@ -854,27 +875,27 @@ public class WindowDecorActionBar extends ActionBar implements
@Override
public boolean isTitleTruncated() {
- return mActionView != null && mActionView.isTitleTruncated();
+ return mDecorToolbar != null && mDecorToolbar.isTitleTruncated();
}
@Override
public void setHomeAsUpIndicator(Drawable indicator) {
- mActionView.setHomeAsUpIndicator(indicator);
+ mDecorToolbar.setNavigationIcon(indicator);
}
@Override
public void setHomeAsUpIndicator(int resId) {
- mActionView.setHomeAsUpIndicator(resId);
+ mDecorToolbar.setNavigationIcon(resId);
}
@Override
public void setHomeActionContentDescription(CharSequence description) {
- mActionView.setHomeActionContentDescription(description);
+ mDecorToolbar.setNavigationContentDescription(description);
}
@Override
public void setHomeActionContentDescription(int resId) {
- mActionView.setHomeActionContentDescription(resId);
+ mDecorToolbar.setNavigationContentDescription(resId);
}
@Override
@@ -938,7 +959,8 @@ public class WindowDecorActionBar extends ActionBar implements
// Clear out the context mode views after the animation finishes
mContextView.closeMode();
- mActionView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ mDecorToolbar.getViewGroup().sendAccessibilityEvent(
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
mOverlayLayout.setHideOnContentScrollEnabled(mHideOnContentScroll);
mActionMode = null;
@@ -1178,28 +1200,27 @@ public class WindowDecorActionBar extends ActionBar implements
@Override
public void setCustomView(View view) {
- mActionView.setCustomNavigationView(view);
+ mDecorToolbar.setCustomView(view);
}
@Override
public void setCustomView(View view, LayoutParams layoutParams) {
view.setLayoutParams(layoutParams);
- mActionView.setCustomNavigationView(view);
+ mDecorToolbar.setCustomView(view);
}
@Override
public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) {
- mActionView.setDropdownAdapter(adapter);
- mActionView.setCallback(callback);
+ mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback));
}
@Override
public int getSelectedNavigationIndex() {
- switch (mActionView.getNavigationMode()) {
+ switch (mDecorToolbar.getNavigationMode()) {
case NAVIGATION_MODE_TABS:
return mSelectedTab != null ? mSelectedTab.getPosition() : -1;
case NAVIGATION_MODE_LIST:
- return mActionView.getDropdownSelectedPosition();
+ return mDecorToolbar.getDropdownSelectedPosition();
default:
return -1;
}
@@ -1207,12 +1228,11 @@ public class WindowDecorActionBar extends ActionBar implements
@Override
public int getNavigationItemCount() {
- switch (mActionView.getNavigationMode()) {
+ switch (mDecorToolbar.getNavigationMode()) {
case NAVIGATION_MODE_TABS:
return mTabs.size();
case NAVIGATION_MODE_LIST:
- SpinnerAdapter adapter = mActionView.getDropdownAdapter();
- return adapter != null ? adapter.getCount() : 0;
+ return mDecorToolbar.getDropdownItemCount();
default:
return 0;
}
@@ -1225,7 +1245,7 @@ public class WindowDecorActionBar extends ActionBar implements
@Override
public void setNavigationMode(int mode) {
- final int oldMode = mActionView.getNavigationMode();
+ final int oldMode = mDecorToolbar.getNavigationMode();
switch (oldMode) {
case NAVIGATION_MODE_TABS:
mSavedTabPosition = getSelectedNavigationIndex();
@@ -1238,7 +1258,7 @@ public class WindowDecorActionBar extends ActionBar implements
mOverlayLayout.requestFitSystemWindows();
}
}
- mActionView.setNavigationMode(mode);
+ mDecorToolbar.setNavigationMode(mode);
switch (mode) {
case NAVIGATION_MODE_TABS:
ensureTabsExist();
@@ -1249,7 +1269,7 @@ public class WindowDecorActionBar extends ActionBar implements
}
break;
}
- mActionView.setCollapsable(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
+ mDecorToolbar.setCollapsible(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
mOverlayLayout.setHasNonEmbeddedTabs(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
}
@@ -1261,30 +1281,30 @@ public class WindowDecorActionBar extends ActionBar implements
@Override
public void setIcon(int resId) {
- mActionView.setIcon(resId);
+ mDecorToolbar.setIcon(resId);
}
@Override
public void setIcon(Drawable icon) {
- mActionView.setIcon(icon);
+ mDecorToolbar.setIcon(icon);
}
public boolean hasIcon() {
- return mActionView.hasIcon();
+ return mDecorToolbar.hasIcon();
}
@Override
public void setLogo(int resId) {
- mActionView.setLogo(resId);
+ mDecorToolbar.setLogo(resId);
}
@Override
public void setLogo(Drawable logo) {
- mActionView.setLogo(logo);
+ mDecorToolbar.setLogo(logo);
}
public boolean hasLogo() {
- return mActionView.hasLogo();
+ return mDecorToolbar.hasLogo();
}
public void setDefaultDisplayHomeAsUpEnabled(boolean enable) {
@@ -1292,4 +1312,24 @@ public class WindowDecorActionBar extends ActionBar implements
setDisplayHomeAsUpEnabled(enable);
}
}
+
+ static class NavItemSelectedListener implements AdapterView.OnItemSelectedListener {
+ private final OnNavigationListener mListener;
+
+ public NavItemSelectedListener(OnNavigationListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ if (mListener != null) {
+ mListener.onNavigationItemSelected(position, id);
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Do nothing
+ }
+ }
}
diff --git a/core/java/com/android/internal/backup/BackupConstants.java b/core/java/com/android/internal/backup/BackupConstants.java
deleted file mode 100644
index 4c276b7..0000000
--- a/core/java/com/android/internal/backup/BackupConstants.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.backup;
-
-/**
- * Constants used internally between the backup manager and its transports
- */
-public class BackupConstants {
- public static final int TRANSPORT_OK = 0;
- public static final int TRANSPORT_ERROR = 1;
- public static final int TRANSPORT_NOT_INITIALIZED = 2;
- public static final int AGENT_ERROR = 3;
- public static final int AGENT_UNKNOWN = 4;
-}
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index 1e37fd9..d10451b 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -178,7 +178,7 @@ interface IBackupTransport {
/**
* Get the data for the application returned by {@link #nextRestorePackage}.
* @param data An open, writable file into which the backup data should be stored.
- * @return the same error codes as {@link #nextRestorePackage}.
+ * @return the same error codes as {@link #startRestore}.
*/
int getRestoreData(in ParcelFileDescriptor outFd);
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 446ef55..7292116 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -18,6 +18,7 @@ package com.android.internal.backup;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupTransport;
import android.app.backup.RestoreSet;
import android.content.ComponentName;
import android.content.Context;
@@ -47,7 +48,7 @@ import static android.system.OsConstants.*;
* later restoring from there. For testing only.
*/
-public class LocalTransport extends IBackupTransport.Stub {
+public class LocalTransport extends BackupTransport {
private static final String TAG = "LocalTransport";
private static final boolean DEBUG = true;
@@ -103,7 +104,7 @@ public class LocalTransport extends IBackupTransport.Stub {
public int initializeDevice() {
if (DEBUG) Log.v(TAG, "wiping all data");
deleteContents(mCurrentSetDir);
- return BackupConstants.TRANSPORT_OK;
+ return BackupTransport.TRANSPORT_OK;
}
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
@@ -165,7 +166,7 @@ public class LocalTransport extends IBackupTransport.Stub {
entity.write(buf, 0, dataSize);
} catch (IOException e) {
Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
- return BackupConstants.TRANSPORT_ERROR;
+ return BackupTransport.TRANSPORT_ERROR;
} finally {
entity.close();
}
@@ -173,11 +174,11 @@ public class LocalTransport extends IBackupTransport.Stub {
entityFile.delete();
}
}
- return BackupConstants.TRANSPORT_OK;
+ return BackupTransport.TRANSPORT_OK;
} catch (IOException e) {
// oops, something went wrong. abort the operation and return error.
Log.v(TAG, "Exception reading backup input:", e);
- return BackupConstants.TRANSPORT_ERROR;
+ return BackupTransport.TRANSPORT_ERROR;
}
}
@@ -207,17 +208,17 @@ public class LocalTransport extends IBackupTransport.Stub {
}
packageDir.delete();
}
- return BackupConstants.TRANSPORT_OK;
+ return BackupTransport.TRANSPORT_OK;
}
public int finishBackup() {
if (DEBUG) Log.v(TAG, "finishBackup()");
- return BackupConstants.TRANSPORT_OK;
+ return BackupTransport.TRANSPORT_OK;
}
// Restore handling
static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 };
- public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
+ public RestoreSet[] getAvailableRestoreSets() {
long[] existing = new long[POSSIBLE_SETS.length + 1];
int num = 0;
@@ -248,7 +249,7 @@ public class LocalTransport extends IBackupTransport.Stub {
mRestorePackage = -1;
mRestoreToken = token;
mRestoreDataDir = new File(mDataDir, Long.toString(token));
- return BackupConstants.TRANSPORT_OK;
+ return BackupTransport.TRANSPORT_OK;
}
public String nextRestorePackage() {
@@ -280,7 +281,7 @@ public class LocalTransport extends IBackupTransport.Stub {
ArrayList<DecodedFilename> blobs = contentsByKey(packageDir);
if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error
Log.e(TAG, "No keys for package: " + packageDir);
- return BackupConstants.TRANSPORT_ERROR;
+ return BackupTransport.TRANSPORT_ERROR;
}
// We expect at least some data if the directory exists in the first place
@@ -301,10 +302,10 @@ public class LocalTransport extends IBackupTransport.Stub {
in.close();
}
}
- return BackupConstants.TRANSPORT_OK;
+ return BackupTransport.TRANSPORT_OK;
} catch (IOException e) {
Log.e(TAG, "Unable to read backup records", e);
- return BackupConstants.TRANSPORT_ERROR;
+ return BackupTransport.TRANSPORT_ERROR;
}
}
diff --git a/core/java/com/android/internal/backup/LocalTransportService.java b/core/java/com/android/internal/backup/LocalTransportService.java
index d05699a..77ac313 100644
--- a/core/java/com/android/internal/backup/LocalTransportService.java
+++ b/core/java/com/android/internal/backup/LocalTransportService.java
@@ -32,6 +32,6 @@ public class LocalTransportService extends Service {
@Override
public IBinder onBind(Intent intent) {
- return sTransport;
+ return sTransport.getBinder();
}
}
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index ba419f9..dab3aff 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -20,6 +20,7 @@ import android.content.pm.PackageManager;
import android.util.Slog;
import java.io.File;
+import java.io.IOException;
/**
* Native libraries helper.
@@ -141,4 +142,18 @@ public class NativeLibraryHelper {
return deletedFiles;
}
+
+ // We don't care about the other return values for now.
+ private static final int BITCODE_PRESENT = 1;
+
+ public static boolean hasRenderscriptBitcode(ApkHandle handle) throws IOException {
+ final int returnVal = hasRenderscriptBitcode(handle.apkHandle);
+ if (returnVal < 0) {
+ throw new IOException("Error scanning APK, code: " + returnVal);
+ }
+
+ return (returnVal == BITCODE_PRESENT);
+ }
+
+ private static native int hasRenderscriptBitcode(long apkHandle);
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
index e3f21cf..7dbde69 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -16,17 +16,17 @@
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.Log;
import android.util.Slog;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
-import java.util.ArrayDeque;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -34,32 +34,21 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import java.util.TreeMap;
/**
* InputMethodSubtypeSwitchingController controls the switching behavior of the subtypes.
+ * <p>
+ * This class is designed to be used from and only from {@link InputMethodManagerService} by using
+ * {@link InputMethodManagerService#mMethodMap} as a global lock.
+ * </p>
*/
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;
@@ -118,6 +107,35 @@ public class InputMethodSubtypeSwitchingController {
}
return mSubtypeName.toString().compareTo(other.mSubtypeName.toString());
}
+
+ @Override
+ public String toString() {
+ return "ImeSubtypeListItem{"
+ + "mImeName=" + mImeName
+ + " mSubtypeName=" + mSubtypeName
+ + " mSubtypeId=" + mSubtypeId
+ + " mIsSystemLocale=" + mIsSystemLocale
+ + " mIsSystemLanguage=" + mIsSystemLanguage
+ + "}";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof ImeSubtypeListItem) {
+ final ImeSubtypeListItem that = (ImeSubtypeListItem)o;
+ if (!Objects.equals(this.mImi, that.mImi)) {
+ return false;
+ }
+ if (this.mSubtypeId != that.mSubtypeId) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
}
private static class InputMethodAndSubtypeList {
@@ -213,102 +231,273 @@ public class InputMethodSubtypeSwitchingController {
}
}
- private final ArrayDeque<SubtypeParams> mTypedSubtypeHistory = new ArrayDeque<SubtypeParams>();
- private final Object mLock = new Object();
- private final InputMethodSettings mSettings;
- private InputMethodAndSubtypeList mSubtypeList;
+ private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) {
+ return subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi,
+ subtype.hashCode()) : NOT_A_SUBTYPE_ID;
+ }
- @VisibleForTesting
- public static ImeSubtypeListItem getNextInputMethodImpl(List<ImeSubtypeListItem> imList,
- boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) {
- if (imi == null) {
- return null;
+ private static class StaticRotationList {
+ private final List<ImeSubtypeListItem> mImeSubtypeList;
+ public StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList) {
+ mImeSubtypeList = imeSubtypeList;
}
- if (imList.size() <= 1) {
- return null;
- }
- // Here we have two rotation groups, depending on the returned boolean value of
- // {@link InputMethodInfo#supportsSwitchingToNextInputMethod()}.
- final boolean expectedValueOfSupportsSwitchingToNextInputMethod =
- imi.supportsSwitchingToNextInputMethod();
- 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);
- // Skip until the current IME/subtype is found.
- if (!isli.mImi.equals(imi) || isli.mSubtypeId != currentSubtypeId) {
- continue;
- }
- // Found the current IME/subtype. Start searching the next IME/subtype from here.
- for (int j = 0; j < N - 1; ++j) {
- final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N);
- // Skip if the candidate doesn't belong to the expected rotation group.
- if (expectedValueOfSupportsSwitchingToNextInputMethod !=
- candidate.mImi.supportsSwitchingToNextInputMethod()) {
- continue;
+
+ /**
+ * Returns the index of the specified input method and subtype in the given list.
+ * @param imi The {@link InputMethodInfo} to be searched.
+ * @param subtype The {@link InputMethodSubtype} to be searched. null if the input method
+ * does not have a subtype.
+ * @return The index in the given list. -1 if not found.
+ */
+ private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) {
+ final int currentSubtypeId = calculateSubtypeId(imi, subtype);
+ final int N = mImeSubtypeList.size();
+ for (int i = 0; i < N; ++i) {
+ final ImeSubtypeListItem isli = mImeSubtypeList.get(i);
+ // Skip until the current IME/subtype is found.
+ if (imi.equals(isli.mImi) && isli.mSubtypeId == currentSubtypeId) {
+ return i;
}
+ }
+ return -1;
+ }
+
+ public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
+ InputMethodInfo imi, InputMethodSubtype subtype) {
+ if (imi == null) {
+ return null;
+ }
+ if (mImeSubtypeList.size() <= 1) {
+ return null;
+ }
+ final int currentIndex = getIndex(imi, subtype);
+ if (currentIndex < 0) {
+ return null;
+ }
+ final int N = mImeSubtypeList.size();
+ for (int offset = 1; offset < N; ++offset) {
+ // Start searching the next IME/subtype from the next of the current index.
+ final int candidateIndex = (currentIndex + offset) % N;
+ final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex);
// Skip if searching inside the current IME only, but the candidate is not
// the current IME.
- if (onlyCurrentIme && !candidate.mImi.equals(imi)) {
+ if (onlyCurrentIme && !imi.equals(candidate.mImi)) {
continue;
}
return candidate;
}
- // No appropriate IME/subtype is found in the list. Give up.
return null;
}
- // The current IME/subtype is not found in the list. Give up.
- return null;
}
- public InputMethodSubtypeSwitchingController(InputMethodSettings settings) {
- mSettings = settings;
+ private static class DynamicRotationList {
+ private static final String TAG = DynamicRotationList.class.getSimpleName();
+ private final List<ImeSubtypeListItem> mImeSubtypeList;
+ private final int[] mUsageHistoryOfSubtypeListItemIndex;
+
+ private DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems) {
+ mImeSubtypeList = imeSubtypeListItems;
+ mUsageHistoryOfSubtypeListItemIndex = new int[mImeSubtypeList.size()];
+ final int N = mImeSubtypeList.size();
+ for (int i = 0; i < N; i++) {
+ mUsageHistoryOfSubtypeListItemIndex[i] = i;
+ }
+ }
+
+ /**
+ * Returns the index of the specified object in
+ * {@link #mUsageHistoryOfSubtypeListItemIndex}.
+ * <p>We call the index of {@link #mUsageHistoryOfSubtypeListItemIndex} as "Usage Rank"
+ * so as not to be confused with the index in {@link #mImeSubtypeList}.
+ * @return -1 when the specified item doesn't belong to {@link #mImeSubtypeList} actually.
+ */
+ private int getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype) {
+ final int currentSubtypeId = calculateSubtypeId(imi, subtype);
+ final int N = mUsageHistoryOfSubtypeListItemIndex.length;
+ for (int usageRank = 0; usageRank < N; usageRank++) {
+ final int subtypeListItemIndex = mUsageHistoryOfSubtypeListItemIndex[usageRank];
+ final ImeSubtypeListItem subtypeListItem =
+ mImeSubtypeList.get(subtypeListItemIndex);
+ if (subtypeListItem.mImi.equals(imi) &&
+ subtypeListItem.mSubtypeId == currentSubtypeId) {
+ return usageRank;
+ }
+ }
+ // Not found in the known IME/Subtype list.
+ return -1;
+ }
+
+ public void onUserAction(InputMethodInfo imi, InputMethodSubtype subtype) {
+ final int currentUsageRank = getUsageRank(imi, subtype);
+ // Do nothing if currentUsageRank == -1 (not found), or currentUsageRank == 0
+ if (currentUsageRank <= 0) {
+ return;
+ }
+ final int currentItemIndex = mUsageHistoryOfSubtypeListItemIndex[currentUsageRank];
+ System.arraycopy(mUsageHistoryOfSubtypeListItemIndex, 0,
+ mUsageHistoryOfSubtypeListItemIndex, 1, currentUsageRank);
+ mUsageHistoryOfSubtypeListItemIndex[0] = currentItemIndex;
+ }
+
+ public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
+ InputMethodInfo imi, InputMethodSubtype subtype) {
+ int currentUsageRank = getUsageRank(imi, subtype);
+ if (currentUsageRank < 0) {
+ if (DEBUG) {
+ Slog.d(TAG, "IME/subtype is not found: " + imi.getId() + ", " + subtype);
+ }
+ return null;
+ }
+ final int N = mUsageHistoryOfSubtypeListItemIndex.length;
+ for (int i = 1; i < N; i++) {
+ final int subtypeListItemRank = (currentUsageRank + i) % N;
+ final int subtypeListItemIndex =
+ mUsageHistoryOfSubtypeListItemIndex[subtypeListItemRank];
+ final ImeSubtypeListItem subtypeListItem =
+ mImeSubtypeList.get(subtypeListItemIndex);
+ if (onlyCurrentIme && !imi.equals(subtypeListItem.mImi)) {
+ continue;
+ }
+ return subtypeListItem;
+ }
+ return null;
+ }
}
- // 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);
+ @VisibleForTesting
+ public static class ControllerImpl {
+ private final DynamicRotationList mSwitchingAwareRotationList;
+ private final StaticRotationList mSwitchingUnawareRotationList;
+
+ public static ControllerImpl createFrom(final ControllerImpl currentInstance,
+ final List<ImeSubtypeListItem> sortedEnabledItems) {
+ DynamicRotationList switchingAwareRotationList = null;
+ {
+ final List<ImeSubtypeListItem> switchingAwareImeSubtypes =
+ filterImeSubtypeList(sortedEnabledItems,
+ true /* supportsSwitchingToNextInputMethod */);
+ if (currentInstance != null &&
+ currentInstance.mSwitchingAwareRotationList != null &&
+ Objects.equals(currentInstance.mSwitchingAwareRotationList.mImeSubtypeList,
+ switchingAwareImeSubtypes)) {
+ // Can reuse the current instance.
+ switchingAwareRotationList = currentInstance.mSwitchingAwareRotationList;
+ }
+ if (switchingAwareRotationList == null) {
+ switchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes);
+ }
+ }
+
+ StaticRotationList switchingUnawareRotationList = null;
+ {
+ final List<ImeSubtypeListItem> switchingUnawareImeSubtypes = filterImeSubtypeList(
+ sortedEnabledItems, false /* supportsSwitchingToNextInputMethod */);
+ if (currentInstance != null &&
+ currentInstance.mSwitchingUnawareRotationList != null &&
+ Objects.equals(
+ currentInstance.mSwitchingUnawareRotationList.mImeSubtypeList,
+ switchingUnawareImeSubtypes)) {
+ // Can reuse the current instance.
+ switchingUnawareRotationList = currentInstance.mSwitchingUnawareRotationList;
+ }
+ if (switchingUnawareRotationList == null) {
+ switchingUnawareRotationList =
+ new StaticRotationList(switchingUnawareImeSubtypes);
+ }
+ }
+
+ return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList);
+ }
+
+ private ControllerImpl(final DynamicRotationList switchingAwareRotationList,
+ final StaticRotationList switchingUnawareRotationList) {
+ mSwitchingAwareRotationList = switchingAwareRotationList;
+ mSwitchingUnawareRotationList = switchingUnawareRotationList;
+ }
+
+ public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi,
+ InputMethodSubtype subtype) {
+ if (imi == null) {
+ return null;
+ }
+ if (imi.supportsSwitchingToNextInputMethod()) {
+ return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
+ subtype);
+ } else {
+ return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
+ subtype);
+ }
+ }
+
+ public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) {
+ if (imi == null) {
return;
}
- if (DEBUG) {
- Slog.d(TAG, "onCommitText: " + imi.getId() + ", " + subtype);
+ if (imi.supportsSwitchingToNextInputMethod()) {
+ mSwitchingAwareRotationList.onUserAction(imi, subtype);
}
- if (REQUIRE_SWITCHING_SUPPORT) {
- if (!imi.supportsSwitchingToNextInputMethod()) {
- Slog.w(TAG, imi.getId() + " doesn't support switching to next input method.");
- return;
+ }
+
+ private static List<ImeSubtypeListItem> filterImeSubtypeList(
+ final List<ImeSubtypeListItem> items,
+ final boolean supportsSwitchingToNextInputMethod) {
+ final ArrayList<ImeSubtypeListItem> result = new ArrayList<>();
+ final int ALL_ITEMS_COUNT = items.size();
+ for (int i = 0; i < ALL_ITEMS_COUNT; i++) {
+ final ImeSubtypeListItem item = items.get(i);
+ if (item.mImi.supportsSwitchingToNextInputMethod() ==
+ supportsSwitchingToNextInputMethod) {
+ result.add(item);
}
}
- if (mTypedSubtypeHistory.size() >= MAX_HISTORY_SIZE) {
- mTypedSubtypeHistory.poll();
+ return result;
+ }
+ }
+
+ private final InputMethodSettings mSettings;
+ private InputMethodAndSubtypeList mSubtypeList;
+ private ControllerImpl mController;
+
+ private InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context) {
+ mSettings = settings;
+ resetCircularListLocked(context);
+ }
+
+ public static InputMethodSubtypeSwitchingController createInstanceLocked(
+ InputMethodSettings settings, Context context) {
+ return new InputMethodSubtypeSwitchingController(settings, context);
+ }
+
+ public void onCommitTextLocked(InputMethodInfo imi, InputMethodSubtype subtype) {
+ if (mController == null) {
+ if (DEBUG) {
+ Log.e(TAG, "mController shouldn't be null.");
}
- mTypedSubtypeHistory.addFirst(new SubtypeParams(imi, subtype));
+ return;
}
+ mController.onUserActionLocked(imi, subtype);
}
public void resetCircularListLocked(Context context) {
- synchronized(mLock) {
- mSubtypeList = new InputMethodAndSubtypeList(context, mSettings);
- }
+ mSubtypeList = new InputMethodAndSubtypeList(context, mSettings);
+ mController = ControllerImpl.createFrom(mController,
+ mSubtypeList.getSortedInputMethodAndSubtypeList());
}
- public ImeSubtypeListItem getNextInputMethod(
- boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) {
- synchronized(mLock) {
- return getNextInputMethodImpl(mSubtypeList.getSortedInputMethodAndSubtypeList(),
- onlyCurrentIme, imi, subtype);
+ public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi,
+ InputMethodSubtype subtype) {
+ if (mController == null) {
+ if (DEBUG) {
+ Log.e(TAG, "mController shouldn't be null.");
+ }
+ return null;
}
+ return mController.getNextInputMethod(onlyCurrentIme, imi, subtype);
}
- public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(boolean showSubtypes,
+ public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(boolean showSubtypes,
boolean inputShown, boolean isScreenLocked) {
- synchronized(mLock) {
- return mSubtypeList.getSortedInputMethodAndSubtypeList(
- showSubtypes, inputShown, isScreenLocked);
- }
+ return mSubtypeList.getSortedInputMethodAndSubtypeList(
+ showSubtypes, inputShown, isScreenLocked);
}
}
diff --git a/core/java/com/android/internal/notification/DemoContactNotificationScorer.java b/core/java/com/android/internal/notification/DemoContactNotificationScorer.java
deleted file mode 100644
index f484724..0000000
--- a/core/java/com/android/internal/notification/DemoContactNotificationScorer.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
-* 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.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.Settings;
-import android.text.SpannableString;
-import android.util.Slog;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * This NotificationScorer bumps up the priority of notifications that contain references to the
- * display names of starred contacts. The references it picks up are spannable strings which, in
- * their entirety, match the display name of some starred contact. The magnitude of the bump ranges
- * from 0 to 15 (assuming NOTIFICATION_PRIORITY_MULTIPLIER = 10) depending on the initial score, and
- * the mapping is defined by priorityBumpMap. In a production version of this scorer, a notification
- * extra will be used to specify contact identifiers.
- */
-
-public class DemoContactNotificationScorer implements NotificationScorer {
- private static final String TAG = "DemoContactNotificationScorer";
- private static final boolean DBG = false;
-
- protected static final boolean ENABLE_CONTACT_SCORER = true;
- private static final String SETTING_ENABLE_SCORER = "contact_scorer_enabled";
- protected boolean mEnabled;
-
- // see NotificationManagerService
- private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10;
-
- private Context mContext;
-
- private static final List<String> RELEVANT_KEYS_LIST = Arrays.asList(
- Notification.EXTRA_INFO_TEXT, Notification.EXTRA_TEXT, Notification.EXTRA_TEXT_LINES,
- Notification.EXTRA_SUB_TEXT, Notification.EXTRA_TITLE
- );
-
- private static final String[] PROJECTION = new String[] {
- ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME
- };
-
- private static final Uri CONTACTS_URI = ContactsContract.Contacts.CONTENT_URI;
-
- private static List<String> extractSpannedStrings(CharSequence charSequence) {
- if (charSequence == null) return Collections.emptyList();
- if (!(charSequence instanceof SpannableString)) {
- return Arrays.asList(charSequence.toString());
- }
- SpannableString spannableString = (SpannableString)charSequence;
- // get all spans
- Object[] ssArr = spannableString.getSpans(0, spannableString.length(), Object.class);
- // spanned string sequences
- ArrayList<String> sss = new ArrayList<String>();
- for (Object spanObj : ssArr) {
- try {
- sss.add(spannableString.subSequence(spannableString.getSpanStart(spanObj),
- spannableString.getSpanEnd(spanObj)).toString());
- } catch(StringIndexOutOfBoundsException e) {
- Slog.e(TAG, "Bad indices when extracting spanned subsequence", e);
- }
- }
- return sss;
- };
-
- private static String getQuestionMarksInParens(int n) {
- StringBuilder sb = new StringBuilder("(");
- for (int i = 0; i < n; i++) {
- if (sb.length() > 1) sb.append(',');
- sb.append('?');
- }
- sb.append(")");
- return sb.toString();
- }
-
- private boolean hasStarredContact(Bundle extras) {
- if (extras == null) return false;
- ArrayList<String> qStrings = new ArrayList<String>();
- // build list to query against the database for display names.
- for (String rk: RELEVANT_KEYS_LIST) {
- if (extras.get(rk) == null) {
- continue;
- } else if (extras.get(rk) instanceof CharSequence) {
- qStrings.addAll(extractSpannedStrings((CharSequence) extras.get(rk)));
- } else if (extras.get(rk) instanceof CharSequence[]) {
- // this is intended for Notification.EXTRA_TEXT_LINES
- for (CharSequence line: (CharSequence[]) extras.get(rk)){
- qStrings.addAll(extractSpannedStrings(line));
- }
- } else {
- Slog.w(TAG, "Strange, the extra " + rk + " is of unexpected type.");
- }
- }
- if (qStrings.isEmpty()) return false;
- String[] qStringsArr = qStrings.toArray(new String[qStrings.size()]);
-
- String selection = ContactsContract.Contacts.DISPLAY_NAME + " IN "
- + getQuestionMarksInParens(qStringsArr.length) + " AND "
- + ContactsContract.Contacts.STARRED+" ='1'";
-
- Cursor c = null;
- try {
- c = mContext.getContentResolver().query(
- CONTACTS_URI, PROJECTION, selection, qStringsArr, null);
- if (c != null) return c.getCount() > 0;
- } catch(Throwable t) {
- Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
- } finally {
- if (c != null) {
- c.close();
- }
- }
- return false;
- }
-
- private final static int clamp(int x, int low, int high) {
- return (x < low) ? low : ((x > high) ? high : x);
- }
-
- 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;
- mEnabled = ENABLE_CONTACT_SCORER && 1 == Settings.Global.getInt(
- mContext.getContentResolver(), SETTING_ENABLE_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;
- }
- boolean hasStarredPriority = hasStarredContact(notification.extras);
-
- if (DBG) {
- if (hasStarredPriority) {
- Slog.v(TAG, "Notification references starred contact. Promoted!");
- } else {
- Slog.v(TAG, "Notification lacks any starred contact reference. Not promoted!");
- }
- }
- if (hasStarredPriority) score = priorityBumpMap(score);
- return score;
- }
-}
-
diff --git a/core/java/com/android/internal/notification/NotificationScorer.java b/core/java/com/android/internal/notification/NotificationScorer.java
deleted file mode 100644
index 863c08c..0000000
--- a/core/java/com/android/internal/notification/NotificationScorer.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
-* 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.notification;
-
-import android.app.Notification;
-import android.content.Context;
-
-public interface NotificationScorer {
-
- public void initialize(Context context);
- public int getScore(Notification notification, int score);
-
-}
diff --git a/core/java/com/android/internal/notification/PeopleNotificationScorer.java b/core/java/com/android/internal/notification/PeopleNotificationScorer.java
deleted file mode 100644
index efb5f63..0000000
--- a/core/java/com/android/internal/notification/PeopleNotificationScorer.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
-* 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/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 1aff190..240d520 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -48,7 +48,6 @@ 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 android.view.Display;
@@ -89,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 = 105 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 106 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -188,8 +187,7 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mShuttingDown;
- HashMap<String, SparseBooleanArray>[] mActiveEvents
- = (HashMap<String, SparseBooleanArray>[]) new HashMap[HistoryItem.EVENT_COUNT];
+ final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
long mHistoryBaseTime;
boolean mHaveBatteryLevel = false;
@@ -237,6 +235,8 @@ public final class BatteryStatsImpl extends BatteryStats {
int mWakeLockNesting;
boolean mWakeLockImportant;
+ boolean mRecordAllWakeLocks;
+ boolean mNoAutoReset;
int mScreenState = Display.STATE_UNKNOWN;
StopwatchTimer mScreenOnTimer;
@@ -247,6 +247,9 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mInteractive;
StopwatchTimer mInteractiveTimer;
+ boolean mLowPowerModeEnabled;
+ StopwatchTimer mLowPowerModeEnabledTimer;
+
boolean mPhoneOn;
StopwatchTimer mPhoneOnTimer;
@@ -325,11 +328,13 @@ public final class BatteryStatsImpl extends BatteryStats {
int mLastDischargeStepLevel;
long mLastDischargeStepTime;
+ int mMinDischargeStepLevel;
int mNumDischargeStepDurations;
final long[] mDischargeStepDurations = new long[MAX_LEVEL_STEPS];
int mLastChargeStepLevel;
long mLastChargeStepTime;
+ int mMaxChargeStepLevel;
int mNumChargeStepDurations;
final long[] mChargeStepDurations = new long[MAX_LEVEL_STEPS];
@@ -884,6 +889,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mLastTime = 0;
mUnpluggedTime = in.readLong();
timeBase.add(this);
+ if (DEBUG) Log.i(TAG, "**** READ TIMER #" + mType + ": mTotalTime=" + mTotalTime);
}
Timer(int type, TimeBase timeBase) {
@@ -914,6 +920,8 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+ if (DEBUG) Log.i(TAG, "**** WRITING TIMER #" + mType + ": mTotalTime="
+ + computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs)));
out.writeInt(mCount);
out.writeInt(mLoadedCount);
out.writeInt(mUnpluggedCount);
@@ -1998,6 +2006,11 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ @Override
+ public void commitCurrentHistoryBatchLocked() {
+ mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+ }
+
void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
if (!mHaveBatteryLevel || !mRecordingHistory) {
return;
@@ -2297,44 +2310,8 @@ public final class BatteryStatsImpl extends BatteryStats {
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);
- }
+ if (!mActiveEvents.updateState(code, name, uid, 0)) {
+ return;
}
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -2348,6 +2325,21 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ public void setRecordAllWakeLocksLocked(boolean enabled) {
+ mRecordAllWakeLocks = enabled;
+ if (!enabled) {
+ // Clear out any existing state.
+ mActiveEvents.removeEvents(HistoryItem.EVENT_WAKE_LOCK);
+ }
+ }
+
+ public void setNoAutoReset(boolean enabled) {
+ mNoAutoReset = enabled;
+ }
+
+ private String mInitialAcquireWakeName;
+ private int mInitialAcquireWakeUid = -1;
+
public void noteStartWakeLocked(int uid, int pid, String name, String historyName, int type,
boolean unimportantForLogging, long elapsedRealtime, long uptime) {
uid = mapUid(uid);
@@ -2355,22 +2347,33 @@ public final class BatteryStatsImpl extends BatteryStats {
// Only care about partial wake locks, since full wake locks
// will be canceled when the user puts the screen to sleep.
aggregateLastWakeupUptimeLocked(uptime);
+ if (historyName == null) {
+ historyName = name;
+ }
+ if (mRecordAllWakeLocks) {
+ if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName,
+ uid, 0)) {
+ addHistoryEventLocked(elapsedRealtime, uptime,
+ HistoryItem.EVENT_WAKE_LOCK_START, historyName, uid);
+ }
+ }
if (mWakeLockNesting == 0) {
mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: "
+ Integer.toHexString(mHistoryCur.states));
mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
- mHistoryCur.wakelockTag.string = historyName != null ? historyName : name;
- mHistoryCur.wakelockTag.uid = uid;
+ mHistoryCur.wakelockTag.string = mInitialAcquireWakeName = historyName;
+ mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = uid;
mWakeLockImportant = !unimportantForLogging;
addHistoryRecordLocked(elapsedRealtime, uptime);
- } else if (!mWakeLockImportant && !unimportantForLogging) {
+ } else if (!mWakeLockImportant && !unimportantForLogging
+ && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE) {
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;
+ mHistoryCur.wakelockTag.string = mInitialAcquireWakeName = historyName;
+ mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = uid;
addHistoryRecordLocked(elapsedRealtime, uptime);
}
mWakeLockImportant = true;
@@ -2386,15 +2389,27 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- public void noteStopWakeLocked(int uid, int pid, String name, int type, long elapsedRealtime,
- long uptime) {
+ public void noteStopWakeLocked(int uid, int pid, String name, String historyName, int type,
+ long elapsedRealtime, long uptime) {
uid = mapUid(uid);
if (type == WAKE_TYPE_PARTIAL) {
mWakeLockNesting--;
+ if (mRecordAllWakeLocks) {
+ if (historyName == null) {
+ historyName = name;
+ }
+ if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName,
+ uid, 0)) {
+ addHistoryEventLocked(elapsedRealtime, uptime,
+ HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, uid);
+ }
+ }
if (mWakeLockNesting == 0) {
mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: "
+ Integer.toHexString(mHistoryCur.states));
+ mInitialAcquireWakeName = null;
+ mInitialAcquireWakeUid = -1;
addHistoryRecordLocked(elapsedRealtime, uptime);
}
}
@@ -2415,8 +2430,8 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- public void noteChangeWakelockFromSourceLocked(WorkSource ws, int pid, String name, int type,
- WorkSource newWs, int newPid, String newName,
+ public void noteChangeWakelockFromSourceLocked(WorkSource ws, int pid, String name,
+ String historyName, int type, WorkSource newWs, int newPid, String newName,
String newHistoryName, int newType, boolean newUnimportantForLogging) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -2430,16 +2445,17 @@ public final class BatteryStatsImpl extends BatteryStats {
}
final int NO = ws.size();
for (int i=0; i<NO; i++) {
- noteStopWakeLocked(ws.get(i), pid, name, type, elapsedRealtime, uptime);
+ noteStopWakeLocked(ws.get(i), pid, name, historyName, type, elapsedRealtime, uptime);
}
}
- public void noteStopWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) {
+ public void noteStopWakeFromSourceLocked(WorkSource ws, int pid, String name,
+ String historyName, int type) {
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, elapsedRealtime, uptime);
+ noteStopWakeLocked(ws.get(i), pid, name, historyName, type, elapsedRealtime, uptime);
}
}
@@ -2708,7 +2724,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(elapsedRealtime);
}
- noteStopWakeLocked(-1, -1, "screen", WAKE_TYPE_PARTIAL,
+ noteStopWakeLocked(-1, -1, "screen", "screen", WAKE_TYPE_PARTIAL,
elapsedRealtime, uptime);
updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), true,
@@ -2804,6 +2820,26 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ public void noteLowPowerMode(boolean enabled) {
+ if (mLowPowerModeEnabled != enabled) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ mLowPowerModeEnabled = enabled;
+ if (enabled) {
+ mHistoryCur.states2 |= HistoryItem.STATE2_LOW_POWER_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Low power mode enabled to: "
+ + Integer.toHexString(mHistoryCur.states2));
+ mLowPowerModeEnabledTimer.startRunningLocked(elapsedRealtime);
+ } else {
+ mHistoryCur.states2 &= ~HistoryItem.STATE2_LOW_POWER_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Low power mode disabled to: "
+ + Integer.toHexString(mHistoryCur.states2));
+ mLowPowerModeEnabledTimer.stopRunningLocked(elapsedRealtime);
+ }
+ addHistoryRecordLocked(elapsedRealtime, uptime);
+ }
+ }
+
public void notePhoneOnLocked() {
if (!mPhoneOn) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -3467,6 +3503,14 @@ public final class BatteryStatsImpl extends BatteryStats {
return mInteractiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
+ @Override public long getLowPowerModeEnabledTime(long elapsedRealtimeUs, int which) {
+ return mLowPowerModeEnabledTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+ }
+
+ @Override public int getLowPowerModeEnabledCount(int which) {
+ return mLowPowerModeEnabledTimer.getCountLocked(which);
+ }
+
@Override public long getPhoneOnTime(long elapsedRealtimeUs, int which) {
return mPhoneOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@@ -5513,7 +5557,9 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mOnBatteryTimeBase);
}
- mPhoneOnTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase);
+ mInteractiveTimer = new StopwatchTimer(null, -9, null, mOnBatteryTimeBase);
+ mLowPowerModeEnabledTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase);
+ mPhoneOnTimer = new StopwatchTimer(null, -3, null, mOnBatteryTimeBase);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, null,
mOnBatteryTimeBase);
@@ -5532,18 +5578,17 @@ public final class BatteryStatsImpl extends BatteryStats {
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);
+ mWifiOnTimer = new StopwatchTimer(null, -4, null, mOnBatteryTimeBase);
+ mGlobalWifiRunningTimer = new StopwatchTimer(null, -5, 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);
+ mBluetoothOnTimer = new StopwatchTimer(null, -6, 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);
- mInteractiveTimer = new StopwatchTimer(null, -8, null, mOnBatteryTimeBase);
+ mAudioOnTimer = new StopwatchTimer(null, -7, null, mOnBatteryTimeBase);
+ mVideoOnTimer = new StopwatchTimer(null, -8, null, mOnBatteryTimeBase);
mOnBattery = mOnBatteryInternal = false;
long uptime = SystemClock.uptimeMillis() * 1000;
long realtime = SystemClock.elapsedRealtime() * 1000;
@@ -5699,7 +5744,8 @@ public final class BatteryStatsImpl extends BatteryStats {
final long lastRealtime = out.time;
final long lastWalltime = out.currentTime;
readHistoryDelta(mHistoryBuffer, out);
- if (out.cmd != HistoryItem.CMD_CURRENT_TIME && lastWalltime != 0) {
+ if (out.cmd != HistoryItem.CMD_CURRENT_TIME
+ && out.cmd != HistoryItem.CMD_RESET && lastWalltime != 0) {
out.currentTime = lastWalltime + (out.time - lastRealtime);
}
return true;
@@ -5788,6 +5834,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mScreenBrightnessTimer[i].reset(false);
}
mInteractiveTimer.reset(false);
+ mLowPowerModeEnabledTimer.reset(false);
mPhoneOnTimer.reset(false);
mAudioOnTimer.reset(false);
mVideoOnTimer.reset(false);
@@ -5845,17 +5892,15 @@ public final class BatteryStatsImpl extends BatteryStats {
private void initActiveHistoryEventsLocked(long elapsedRealtimeMs, long uptimeMs) {
for (int i=0; i<HistoryItem.EVENT_COUNT; i++) {
- HashMap<String, SparseBooleanArray> active = mActiveEvents[i];
+ HashMap<String, SparseIntArray> active = mActiveEvents.getStateForEvent(i);
if (active == null) {
continue;
}
- for (HashMap.Entry<String, SparseBooleanArray> ent : active.entrySet()) {
- SparseBooleanArray uids = ent.getValue();
+ for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
+ SparseIntArray uids = ent.getValue();
for (int j=0; j<uids.size(); j++) {
- if (uids.valueAt(j)) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, i, ent.getKey(),
- uids.keyAt(j));
- }
+ addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, i, ent.getKey(),
+ uids.keyAt(j));
}
}
}
@@ -5910,9 +5955,9 @@ public final class BatteryStatsImpl extends BatteryStats {
// 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
+ if (!mNoAutoReset && (oldStatus == BatteryManager.BATTERY_STATUS_FULL
|| level >= 90
- || (mDischargeCurrentLevel < 20 && level >= 80)) {
+ || (mDischargeCurrentLevel < 20 && level >= 80))) {
doWrite = true;
resetAllStatsLocked();
mDischargeStartLevel = level;
@@ -5920,6 +5965,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mNumDischargeStepDurations = 0;
}
mLastDischargeStepLevel = level;
+ mMinDischargeStepLevel = level;
mLastDischargeStepTime = -1;
pullPendingStateUpdatesLocked();
mHistoryCur.batteryLevel = (byte)level;
@@ -5958,6 +6004,7 @@ public final class BatteryStatsImpl extends BatteryStats {
updateTimeBasesLocked(false, !screenOn, uptime, realtime);
mNumChargeStepDurations = 0;
mLastChargeStepLevel = level;
+ mMaxChargeStepLevel = level;
mLastChargeStepTime = -1;
}
if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) {
@@ -5971,7 +6018,8 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean reset) {
mRecordingHistory = true;
mHistoryCur.currentTime = System.currentTimeMillis();
- addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_CURRENT_TIME,
+ addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs,
+ reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME,
mHistoryCur);
mHistoryCur.currentTime = 0;
if (reset) {
@@ -6078,19 +6126,21 @@ public final class BatteryStatsImpl extends BatteryStats {
addHistoryRecordLocked(elapsedRealtime, uptime);
}
if (onBattery) {
- if (mLastDischargeStepLevel != level) {
+ if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) {
mNumDischargeStepDurations = addLevelSteps(mDischargeStepDurations,
mNumDischargeStepDurations, mLastDischargeStepTime,
mLastDischargeStepLevel - level, elapsedRealtime);
mLastDischargeStepLevel = level;
+ mMinDischargeStepLevel = level;
mLastDischargeStepTime = elapsedRealtime;
}
} else {
- if (mLastChargeStepLevel != level) {
+ if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) {
mNumChargeStepDurations = addLevelSteps(mChargeStepDurations,
mNumChargeStepDurations, mLastChargeStepTime,
level - mLastChargeStepLevel, elapsedRealtime);
mLastChargeStepLevel = level;
+ mMaxChargeStepLevel = level;
mLastChargeStepTime = elapsedRealtime;
}
}
@@ -6939,6 +6989,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mInteractive = false;
mInteractiveTimer.readSummaryFromParcelLocked(in);
mPhoneOn = false;
+ mLowPowerModeEnabledTimer.readSummaryFromParcelLocked(in);
mPhoneOnTimer.readSummaryFromParcelLocked(in);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
mPhoneSignalStrengthsTimer[i].readSummaryFromParcelLocked(in);
@@ -7193,6 +7244,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
}
mInteractiveTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ mLowPowerModeEnabledTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
mPhoneSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
@@ -7454,8 +7506,11 @@ public final class BatteryStatsImpl extends BatteryStats {
mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mOnBatteryTimeBase,
in);
}
+ mInteractive = false;
+ mInteractiveTimer = new StopwatchTimer(null, -9, null, mOnBatteryTimeBase, in);
mPhoneOn = false;
- mPhoneOnTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in);
+ mLowPowerModeEnabledTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in);
+ mPhoneOnTimer = new StopwatchTimer(null, -3, null, mOnBatteryTimeBase, in);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i,
null, mOnBatteryTimeBase, in);
@@ -7477,25 +7532,23 @@ public final class BatteryStatsImpl extends BatteryStats {
mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase, in);
mWifiOn = false;
- mWifiOnTimer = new StopwatchTimer(null, -3, null, mOnBatteryTimeBase, in);
+ mWifiOnTimer = new StopwatchTimer(null, -4, null, mOnBatteryTimeBase, in);
mGlobalWifiRunning = false;
- mGlobalWifiRunningTimer = new StopwatchTimer(null, -4, null, mOnBatteryTimeBase, in);
+ mGlobalWifiRunningTimer = new StopwatchTimer(null, -5, 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, -5, null, mOnBatteryTimeBase, in);
+ mBluetoothOnTimer = new StopwatchTimer(null, -6, null, mOnBatteryTimeBase, in);
for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
mBluetoothStateTimer[i] = new StopwatchTimer(null, -500-i,
null, mOnBatteryTimeBase, in);
}
mAudioOn = false;
- mAudioOnTimer = new StopwatchTimer(null, -6, null, mOnBatteryTimeBase);
+ mAudioOnTimer = new StopwatchTimer(null, -7, null, mOnBatteryTimeBase);
mVideoOn = false;
- mVideoOnTimer = new StopwatchTimer(null, -7, null, mOnBatteryTimeBase);
- mInteractive = false;
- mInteractiveTimer = new StopwatchTimer(null, -8, null, mOnBatteryTimeBase, in);
+ mVideoOnTimer = new StopwatchTimer(null, -8, null, mOnBatteryTimeBase);
mDischargeUnplugLevel = in.readInt();
mDischargePlugLevel = in.readInt();
mDischargeCurrentLevel = in.readInt();
@@ -7594,6 +7647,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mScreenBrightnessTimer[i].writeToParcel(out, uSecRealtime);
}
mInteractiveTimer.writeToParcel(out, uSecRealtime);
+ mLowPowerModeEnabledTimer.writeToParcel(out, uSecRealtime);
mPhoneOnTimer.writeToParcel(out, uSecRealtime);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
mPhoneSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime);
@@ -7712,6 +7766,8 @@ public final class BatteryStatsImpl extends BatteryStats {
}
pr.println("*** Interactive timer:");
mInteractiveTimer.logState(pr, " ");
+ pr.println("*** Low power mode timer:");
+ mLowPowerModeEnabledTimer.logState(pr, " ");
pr.println("*** Phone timer:");
mPhoneOnTimer.logState(pr, " ");
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java
index 40834ba..17685fd 100644
--- a/core/java/com/android/internal/os/HandlerCaller.java
+++ b/core/java/com/android/internal/os/HandlerCaller.java
@@ -22,9 +22,6 @@ import android.os.Looper;
import android.os.Message;
public class HandlerCaller {
-
- public final Context mContext;
-
final Looper mMainLooper;
final Handler mH;
@@ -47,7 +44,6 @@ public class HandlerCaller {
public HandlerCaller(Context context, Looper looper, Callback callback,
boolean asyncHandler) {
- mContext = context;
mMainLooper = looper != null ? looper : context.getMainLooper();
mH = new MyHandler(mMainLooper, asyncHandler);
mCallback = callback;
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 3ea749e..5ce658b 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -23,6 +23,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.net.LocalServerSocket;
import android.opengl.EGL14;
+import android.os.Build;
import android.os.Debug;
import android.os.Process;
import android.os.SystemClock;
@@ -505,7 +506,7 @@ public class ZygoteInit {
/**
* Prepare the arguments and fork for the system server process.
*/
- private static boolean startSystemServer()
+ private static boolean startSystemServer(String abiList, String socketName)
throws MethodAndArgsCaller, RuntimeException {
long capabilities = posixCapabilitiesAsBits(
OsConstants.CAP_BLOCK_SUSPEND,
@@ -553,6 +554,10 @@ public class ZygoteInit {
/* For child process */
if (pid == 0) {
+ if (hasSecondZygote(abiList)) {
+ waitForSecondaryZygote(socketName);
+ }
+
handleSystemServerProcess(parsedArgs);
}
@@ -615,7 +620,7 @@ public class ZygoteInit {
Trace.setTracingEnabled(false);
if (startSystemServer) {
- startSystemServer();
+ startSystemServer(abiList, socketName);
}
Log.i(TAG, "Accepting command socket connections");
@@ -632,6 +637,36 @@ public class ZygoteInit {
}
/**
+ * Return {@code true} if this device configuration has another zygote.
+ *
+ * We determine this by comparing the device ABI list with this zygotes
+ * list. If this zygote supports all ABIs this device supports, there won't
+ * be another zygote.
+ */
+ private static boolean hasSecondZygote(String abiList) {
+ return !SystemProperties.get("ro.product.cpu.abilist").equals(abiList);
+ }
+
+ private static void waitForSecondaryZygote(String socketName) {
+ String otherZygoteName = Process.ZYGOTE_SOCKET.equals(socketName) ?
+ Process.SECONDARY_ZYGOTE_SOCKET : Process.ZYGOTE_SOCKET;
+ while (true) {
+ try {
+ final Process.ZygoteState zs = Process.ZygoteState.connect(otherZygoteName);
+ zs.close();
+ break;
+ } catch (IOException ioe) {
+ Log.w(TAG, "Got error connecting to zygote, retrying. msg= " + ioe.getMessage());
+ }
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ie) {
+ }
+ }
+ }
+
+ /**
* Runs the zygote process's select loop. Accepts new connections as
* they happen, and reads commands from connections one spawn-request's
* worth at a time.
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index b78c70f..a5421f5 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -56,4 +56,13 @@ interface IKeyguardService {
oneway void dispatch(in MotionEvent event);
oneway void launchCamera();
oneway void onBootCompleted();
+
+ /**
+ * Notifies that the activity behind has now been drawn and it's safe to remove the wallpaper
+ * and keyguard flag.
+ *
+ * @param startTime the start time of the animation in uptime milliseconds
+ * @param fadeoutDuration the duration of the exit animation, in milliseconds
+ */
+ oneway void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 75feb5d..a01e9b7 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -24,9 +24,9 @@ oneway interface IStatusBar
{
void setIcon(int index, in StatusBarIcon icon);
void removeIcon(int index);
- void addNotification(IBinder key, in StatusBarNotification notification);
- void updateNotification(IBinder key, in StatusBarNotification notification);
- void removeNotification(IBinder key);
+ void addNotification(in StatusBarNotification notification);
+ void updateNotification(in StatusBarNotification notification);
+ void removeNotification(String key);
void disable(int state);
void animateExpandNotificationsPanel();
void animateExpandSettingsPanel();
@@ -36,9 +36,12 @@ oneway interface IStatusBar
void setImeWindowStatus(in IBinder token, int vis, int backDisposition,
boolean showImeSwitcher);
void setHardKeyboardStatus(boolean available, boolean enabled);
+ void setWindowState(int window, int state);
+
+ void showRecentApps(boolean triggeredFromAltTab);
+ void hideRecentApps(boolean triggeredFromAltTab);
void toggleRecentApps();
void preloadRecentApps();
void cancelPreloadRecentApps();
- void setWindowState(int window, int state);
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index cf334c3..a3b417f 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -39,8 +39,8 @@ interface IStatusBarService
// ---- Methods below are for use by the status bar policy services ----
// You need the STATUS_BAR_SERVICE permission
void registerStatusBar(IStatusBar callbacks, out StatusBarIconList iconList,
- out List<IBinder> notificationKeys, out List<StatusBarNotification> notifications,
- out int[] switches, out List<IBinder> binders);
+ out List<StatusBarNotification> notifications, out int[] switches,
+ out List<IBinder> binders);
void onPanelRevealed();
void onPanelHidden();
void onNotificationClick(String key);
@@ -52,8 +52,11 @@ interface IStatusBarService
in String[] newlyVisibleKeys, in String[] noLongerVisibleKeys);
void setSystemUiVisibility(int vis, int mask);
void setHardKeyboardEnabled(boolean enabled);
+ void setWindowState(int window, int state);
+
+ void showRecentApps(boolean triggeredFromAltTab);
+ void hideRecentApps(boolean triggeredFromAltTab);
void toggleRecentApps();
void preloadRecentApps();
void cancelPreloadRecentApps();
- void setWindowState(int window, int state);
}
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index d177410..d66ef83 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -117,6 +117,13 @@ public class ArrayUtils
}
/**
+ * Checks if given array is null or has zero elements.
+ */
+ public static <T> boolean isEmpty(T[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
* Checks that value is present as at least one of the elements of the array.
* @param array the array to check in
* @param value the value to check for
@@ -162,6 +169,15 @@ public class ArrayUtils
return false;
}
+ public static boolean contains(long[] array, long value) {
+ for (long element : array) {
+ if (element == value) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public static long total(long[] array) {
long total = 0;
for (long value : array) {
@@ -222,6 +238,14 @@ public class ArrayUtils
return array;
}
+ /**
+ * Appends a new value to a copy of the array and returns the copy. If
+ * the value is already present, the original array is returned
+ * @param cur The original array, or null to represent an empty array.
+ * @param val The value to add.
+ * @return A new array that contains all of the values of the original array
+ * with the new value added, or the original array.
+ */
public static int[] appendInt(int[] cur, int val) {
if (cur == null) {
return new int[] { val };
@@ -257,4 +281,48 @@ public class ArrayUtils
}
return cur;
}
+
+ /**
+ * Appends a new value to a copy of the array and returns the copy. If
+ * the value is already present, the original array is returned
+ * @param cur The original array, or null to represent an empty array.
+ * @param val The value to add.
+ * @return A new array that contains all of the values of the original array
+ * with the new value added, or the original array.
+ */
+ public static long[] appendLong(long[] cur, long val) {
+ if (cur == null) {
+ return new long[] { val };
+ }
+ final int N = cur.length;
+ for (int i = 0; i < N; i++) {
+ if (cur[i] == val) {
+ return cur;
+ }
+ }
+ long[] ret = new long[N + 1];
+ System.arraycopy(cur, 0, ret, 0, N);
+ ret[N] = val;
+ return ret;
+ }
+
+ public static long[] removeLong(long[] cur, long val) {
+ if (cur == null) {
+ return null;
+ }
+ final int N = cur.length;
+ for (int i = 0; i < N; i++) {
+ if (cur[i] == val) {
+ long[] ret = new long[N - 1];
+ if (i > 0) {
+ System.arraycopy(cur, 0, ret, 0, i);
+ }
+ if (i < (N - 1)) {
+ System.arraycopy(cur, i + 1, ret, i, N - i - 1);
+ }
+ return ret;
+ }
+ }
+ return cur;
+ }
}
diff --git a/core/java/com/android/internal/util/AsyncChannel.java b/core/java/com/android/internal/util/AsyncChannel.java
index 52281d9..34f62ba 100644
--- a/core/java/com/android/internal/util/AsyncChannel.java
+++ b/core/java/com/android/internal/util/AsyncChannel.java
@@ -450,6 +450,7 @@ public class AsyncChannel {
public void disconnect() {
if ((mConnection != null) && (mSrcContext != null)) {
mSrcContext.unbindService(mConnection);
+ mConnection = null;
}
try {
// Send the DISCONNECTED, although it may not be received
@@ -463,10 +464,12 @@ public class AsyncChannel {
// Tell source we're disconnected.
if (mSrcHandler != null) {
replyDisconnected(STATUS_SUCCESSFUL);
+ mSrcHandler = null;
}
// Unlink only when bindService isn't used
if (mConnection == null && mDstMessenger != null && mDeathMonitor!= null) {
mDstMessenger.getBinder().unlinkToDeath(mDeathMonitor, 0);
+ mDeathMonitor = null;
}
}
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index f6722a6..c0d1e88 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -183,6 +183,33 @@ public class Preconditions {
}
/**
+ * Ensures that the argument int value is within the inclusive range.
+ *
+ * @param value a int value
+ * @param lower the lower endpoint of the inclusive range
+ * @param upper the upper endpoint of the inclusive range
+ * @param valueName the name of the argument to use if the check fails
+ *
+ * @return the validated int value
+ *
+ * @throws IllegalArgumentException if {@code value} was not within the range
+ */
+ public static int checkArgumentInRange(int value, int lower, int upper,
+ String valueName) {
+ if (value < lower) {
+ throw new IllegalArgumentException(
+ String.format(
+ "%s is out of range of [%d, %d] (too low)", valueName, lower, upper));
+ } else if (value > upper) {
+ throw new IllegalArgumentException(
+ String.format(
+ "%s is out of range of [%d, %d] (too high)", valueName, lower, upper));
+ }
+
+ return value;
+ }
+
+ /**
* Ensures that the array is not {@code null}, and none if its elements are {@code null}.
*
* @param value an array of boxed objects
diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java
index bc92c4a..af966b1 100644
--- a/core/java/com/android/internal/util/Protocol.java
+++ b/core/java/com/android/internal/util/Protocol.java
@@ -48,6 +48,8 @@ public class Protocol {
public static final int BASE_WIFI_CONTROLLER = 0x00026000;
public static final int BASE_WIFI_SCANNER = 0x00027000;
public static final int BASE_WIFI_SCANNER_SERVICE = 0x00027100;
+ public static final int BASE_WIFI_PASSPOINT_MANAGER = 0x00028000;
+ public static final int BASE_WIFI_PASSPOINT_SERVICE = 0x00028100;
public static final int BASE_DHCP = 0x00030000;
public static final int BASE_DATA_CONNECTION = 0x00040000;
public static final int BASE_DATA_CONNECTION_AC = 0x00041000;
@@ -55,5 +57,9 @@ public class Protocol {
public static final int BASE_DNS_PINGER = 0x00050000;
public static final int BASE_NSD_MANAGER = 0x00060000;
public static final int BASE_NETWORK_STATE_TRACKER = 0x00070000;
+ public static final int BASE_CONNECTIVITY_MANAGER = 0x00080000;
+ public static final int BASE_NETWORK_AGENT = 0x00081000;
+ public static final int BASE_NETWORK_MONITOR = 0x00082000;
+ public static final int BASE_NETWORK_FACTORY = 0x00083000;
//TODO: define all used protocols
}
diff --git a/core/java/com/android/internal/util/VirtualRefBasePtr.java b/core/java/com/android/internal/util/VirtualRefBasePtr.java
new file mode 100644
index 0000000..52306f1
--- /dev/null
+++ b/core/java/com/android/internal/util/VirtualRefBasePtr.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+/**
+ * Helper class that contains a strong reference to a VirtualRefBase native
+ * object. This will incStrong in the ctor, and decStrong in the finalizer
+ */
+public final class VirtualRefBasePtr {
+ private long mNativePtr;
+
+ public VirtualRefBasePtr(long ptr) {
+ mNativePtr = ptr;
+ nIncStrong(mNativePtr);
+ }
+
+ public long get() {
+ return mNativePtr;
+ }
+
+ public void release() {
+ if (mNativePtr != 0) {
+ nDecStrong(mNativePtr);
+ mNativePtr = 0;
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ release();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private static native void nIncStrong(long ptr);
+ private static native void nDecStrong(long ptr);
+}
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
index b35de93..dca9921 100644
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -220,28 +220,74 @@ public class XmlUtils {
* @see #readMapXml
*/
public static final void writeMapXml(Map val, String name, XmlSerializer out)
- throws XmlPullParserException, java.io.IOException
- {
+ throws XmlPullParserException, java.io.IOException {
+ writeMapXml(val, name, out, null);
+ }
+
+ /**
+ * Flatten a Map into an XmlSerializer. The map can later be read back
+ * with readThisMapXml().
+ *
+ * @param val The map to be flattened.
+ * @param name Name attribute to include with this list's tag, or null for
+ * none.
+ * @param out XmlSerializer to write the map into.
+ * @param callback Method to call when an Object type is not recognized.
+ *
+ * @see #writeMapXml(Map, OutputStream)
+ * @see #writeListXml
+ * @see #writeValueXml
+ * @see #readMapXml
+ *
+ * @hide
+ */
+ public static final void writeMapXml(Map val, String name, XmlSerializer out,
+ WriteMapCallback callback) throws XmlPullParserException, java.io.IOException {
+
if (val == null) {
out.startTag(null, "null");
out.endTag(null, "null");
return;
}
- Set s = val.entrySet();
- Iterator i = s.iterator();
-
out.startTag(null, "map");
if (name != null) {
out.attribute(null, "name", name);
}
+ writeMapXml(val, out, callback);
+
+ out.endTag(null, "map");
+ }
+
+ /**
+ * Flatten a Map into an XmlSerializer. The map can later be read back
+ * with readThisMapXml(). This method presumes that the start tag and
+ * name attribute have already been written and does not write an end tag.
+ *
+ * @param val The map to be flattened.
+ * @param out XmlSerializer to write the map into.
+ *
+ * @see #writeMapXml(Map, OutputStream)
+ * @see #writeListXml
+ * @see #writeValueXml
+ * @see #readMapXml
+ *
+ * @hide
+ */
+ public static final void writeMapXml(Map val, XmlSerializer out,
+ WriteMapCallback callback) throws XmlPullParserException, java.io.IOException {
+ if (val == null) {
+ return;
+ }
+
+ Set s = val.entrySet();
+ Iterator i = s.iterator();
+
while (i.hasNext()) {
Map.Entry e = (Map.Entry)i.next();
- writeValueXml(e.getValue(), (String)e.getKey(), out);
+ writeValueXml(e.getValue(), (String)e.getKey(), out, callback);
}
-
- out.endTag(null, "map");
}
/**
@@ -387,6 +433,123 @@ public class XmlUtils {
}
/**
+ * Flatten a long[] into an XmlSerializer. The list can later be read back
+ * with readThisLongArrayXml().
+ *
+ * @param val The long array to be flattened.
+ * @param name Name attribute to include with this array's tag, or null for
+ * none.
+ * @param out XmlSerializer to write the array into.
+ *
+ * @see #writeMapXml
+ * @see #writeValueXml
+ * @see #readThisIntArrayXml
+ */
+ public static final void writeLongArrayXml(long[] val, String name, XmlSerializer out)
+ throws XmlPullParserException, java.io.IOException {
+
+ if (val == null) {
+ out.startTag(null, "null");
+ out.endTag(null, "null");
+ return;
+ }
+
+ out.startTag(null, "long-array");
+ if (name != null) {
+ out.attribute(null, "name", name);
+ }
+
+ final int N = val.length;
+ out.attribute(null, "num", Integer.toString(N));
+
+ for (int i=0; i<N; i++) {
+ out.startTag(null, "item");
+ out.attribute(null, "value", Long.toString(val[i]));
+ out.endTag(null, "item");
+ }
+
+ out.endTag(null, "long-array");
+ }
+
+ /**
+ * Flatten a double[] into an XmlSerializer. The list can later be read back
+ * with readThisDoubleArrayXml().
+ *
+ * @param val The double array to be flattened.
+ * @param name Name attribute to include with this array's tag, or null for
+ * none.
+ * @param out XmlSerializer to write the array into.
+ *
+ * @see #writeMapXml
+ * @see #writeValueXml
+ * @see #readThisIntArrayXml
+ */
+ public static final void writeDoubleArrayXml(double[] val, String name, XmlSerializer out)
+ throws XmlPullParserException, java.io.IOException {
+
+ if (val == null) {
+ out.startTag(null, "null");
+ out.endTag(null, "null");
+ return;
+ }
+
+ out.startTag(null, "double-array");
+ if (name != null) {
+ out.attribute(null, "name", name);
+ }
+
+ final int N = val.length;
+ out.attribute(null, "num", Integer.toString(N));
+
+ for (int i=0; i<N; i++) {
+ out.startTag(null, "item");
+ out.attribute(null, "value", Double.toString(val[i]));
+ out.endTag(null, "item");
+ }
+
+ out.endTag(null, "double-array");
+ }
+
+ /**
+ * Flatten a String[] into an XmlSerializer. The list can later be read back
+ * with readThisStringArrayXml().
+ *
+ * @param val The long array to be flattened.
+ * @param name Name attribute to include with this array's tag, or null for
+ * none.
+ * @param out XmlSerializer to write the array into.
+ *
+ * @see #writeMapXml
+ * @see #writeValueXml
+ * @see #readThisIntArrayXml
+ */
+ public static final void writeStringArrayXml(String[] val, String name, XmlSerializer out)
+ throws XmlPullParserException, java.io.IOException {
+
+ if (val == null) {
+ out.startTag(null, "null");
+ out.endTag(null, "null");
+ return;
+ }
+
+ out.startTag(null, "string-array");
+ if (name != null) {
+ out.attribute(null, "name", name);
+ }
+
+ final int N = val.length;
+ out.attribute(null, "num", Integer.toString(N));
+
+ for (int i=0; i<N; i++) {
+ out.startTag(null, "item");
+ out.attribute(null, "value", val[i]);
+ out.endTag(null, "item");
+ }
+
+ out.endTag(null, "string-array");
+ }
+
+ /**
* Flatten an object's value into an XmlSerializer. The value can later
* be read back with readThisValueXml().
*
@@ -403,8 +566,29 @@ public class XmlUtils {
* @see #readValueXml
*/
public static final void writeValueXml(Object v, String name, XmlSerializer out)
- throws XmlPullParserException, java.io.IOException
- {
+ throws XmlPullParserException, java.io.IOException {
+ writeValueXml(v, name, out, null);
+ }
+
+ /**
+ * Flatten an object's value into an XmlSerializer. The value can later
+ * be read back with readThisValueXml().
+ *
+ * Currently supported value types are: null, String, Integer, Long,
+ * Float, Double Boolean, Map, List.
+ *
+ * @param v The object to be flattened.
+ * @param name Name attribute to include with this value's tag, or null
+ * for none.
+ * @param out XmlSerializer to write the object into.
+ * @param callback Handler for Object types not recognized.
+ *
+ * @see #writeMapXml
+ * @see #writeListXml
+ * @see #readValueXml
+ */
+ private static final void writeValueXml(Object v, String name, XmlSerializer out,
+ WriteMapCallback callback) throws XmlPullParserException, java.io.IOException {
String typeStr;
if (v == null) {
out.startTag(null, "null");
@@ -437,14 +621,23 @@ public class XmlUtils {
} else if (v instanceof int[]) {
writeIntArrayXml((int[])v, name, out);
return;
+ } else if (v instanceof long[]) {
+ writeLongArrayXml((long[])v, name, out);
+ return;
+ } else if (v instanceof double[]) {
+ writeDoubleArrayXml((double[])v, name, out);
+ return;
+ } else if (v instanceof String[]) {
+ writeStringArrayXml((String[])v, name, out);
+ return;
} else if (v instanceof Map) {
writeMapXml((Map)v, name, out);
return;
} else if (v instanceof List) {
- writeListXml((List)v, name, out);
+ writeListXml((List) v, name, out);
return;
} else if (v instanceof Set) {
- writeSetXml((Set)v, name, out);
+ writeSetXml((Set) v, name, out);
return;
} else if (v instanceof CharSequence) {
// XXX This is to allow us to at least write something if
@@ -457,6 +650,9 @@ public class XmlUtils {
out.text(v.toString());
out.endTag(null, "string");
return;
+ } else if (callback != null) {
+ callback.writeUnknownObject(v, name, out);
+ return;
} else {
throw new RuntimeException("writeValueXml: unable to write value " + v);
}
@@ -550,14 +746,35 @@ public class XmlUtils {
* @see #readMapXml
*/
public static final HashMap<String, ?> readThisMapXml(XmlPullParser parser, String endTag,
- String[] name) throws XmlPullParserException, java.io.IOException
+ String[] name) throws XmlPullParserException, java.io.IOException {
+ return readThisMapXml(parser, endTag, name, null);
+ }
+
+ /**
+ * Read a HashMap object from an XmlPullParser. The XML data could
+ * previously have been generated by writeMapXml(). The XmlPullParser
+ * must be positioned <em>after</em> the tag that begins the map.
+ *
+ * @param parser The XmlPullParser from which to read the map data.
+ * @param endTag Name of the tag that will end the map, usually "map".
+ * @param name An array of one string, used to return the name attribute
+ * of the map's tag.
+ *
+ * @return HashMap The newly generated map.
+ *
+ * @see #readMapXml
+ * @hide
+ */
+ public static final HashMap<String, ?> readThisMapXml(XmlPullParser parser, String endTag,
+ String[] name, ReadMapCallback callback)
+ throws XmlPullParserException, java.io.IOException
{
HashMap<String, Object> map = new HashMap<String, Object>();
int eventType = parser.getEventType();
do {
if (eventType == parser.START_TAG) {
- Object val = readThisValueXml(parser, name);
+ Object val = readThisValueXml(parser, name, callback);
map.put(name[0], val);
} else if (eventType == parser.END_TAG) {
if (parser.getName().equals(endTag)) {
@@ -587,15 +804,34 @@ public class XmlUtils {
*
* @see #readListXml
*/
- public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, String[] name)
- throws XmlPullParserException, java.io.IOException
- {
+ public static final ArrayList readThisListXml(XmlPullParser parser, String endTag,
+ String[] name) throws XmlPullParserException, java.io.IOException {
+ return readThisListXml(parser, endTag, name, null);
+ }
+
+ /**
+ * Read an ArrayList object from an XmlPullParser. The XML data could
+ * previously have been generated by writeListXml(). The XmlPullParser
+ * must be positioned <em>after</em> the tag that begins the list.
+ *
+ * @param parser The XmlPullParser from which to read the list data.
+ * @param endTag Name of the tag that will end the list, usually "list".
+ * @param name An array of one string, used to return the name attribute
+ * of the list's tag.
+ *
+ * @return HashMap The newly generated list.
+ *
+ * @see #readListXml
+ */
+ private static final ArrayList readThisListXml(XmlPullParser parser, String endTag,
+ String[] name, ReadMapCallback callback)
+ throws XmlPullParserException, java.io.IOException {
ArrayList list = new ArrayList();
int eventType = parser.getEventType();
do {
if (eventType == parser.START_TAG) {
- Object val = readThisValueXml(parser, name);
+ Object val = readThisValueXml(parser, name, callback);
list.add(val);
//System.out.println("Adding to list: " + val);
} else if (eventType == parser.END_TAG) {
@@ -611,7 +847,29 @@ public class XmlUtils {
throw new XmlPullParserException(
"Document ended before " + endTag + " end tag");
}
-
+
+ /**
+ * Read a HashSet object from an XmlPullParser. The XML data could previously
+ * have been generated by writeSetXml(). The XmlPullParser must be positioned
+ * <em>after</em> the tag that begins the set.
+ *
+ * @param parser The XmlPullParser from which to read the set data.
+ * @param endTag Name of the tag that will end the set, usually "set".
+ * @param name An array of one string, used to return the name attribute
+ * of the set's tag.
+ *
+ * @return HashSet The newly generated set.
+ *
+ * @throws XmlPullParserException
+ * @throws java.io.IOException
+ *
+ * @see #readSetXml
+ */
+ public static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name)
+ throws XmlPullParserException, java.io.IOException {
+ return readThisSetXml(parser, endTag, name, null);
+ }
+
/**
* Read a HashSet object from an XmlPullParser. The XML data could previously
* have been generated by writeSetXml(). The XmlPullParser must be positioned
@@ -628,15 +886,16 @@ public class XmlUtils {
* @throws java.io.IOException
*
* @see #readSetXml
+ * @hide
*/
- public static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name)
- throws XmlPullParserException, java.io.IOException {
+ private static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name,
+ ReadMapCallback callback) throws XmlPullParserException, java.io.IOException {
HashSet set = new HashSet();
int eventType = parser.getEventType();
do {
if (eventType == parser.START_TAG) {
- Object val = readThisValueXml(parser, name);
+ Object val = readThisValueXml(parser, name, callback);
set.add(val);
//System.out.println("Adding to set: " + val);
} else if (eventType == parser.END_TAG) {
@@ -681,6 +940,7 @@ public class XmlUtils {
throw new XmlPullParserException(
"Not a number in num attribute in byte-array");
}
+ parser.next();
int[] array = new int[num];
int i = 0;
@@ -722,6 +982,187 @@ public class XmlUtils {
}
/**
+ * Read a long[] object from an XmlPullParser. The XML data could
+ * previously have been generated by writeLongArrayXml(). The XmlPullParser
+ * must be positioned <em>after</em> the tag that begins the list.
+ *
+ * @param parser The XmlPullParser from which to read the list data.
+ * @param endTag Name of the tag that will end the list, usually "list".
+ * @param name An array of one string, used to return the name attribute
+ * of the list's tag.
+ *
+ * @return Returns a newly generated long[].
+ *
+ * @see #readListXml
+ */
+ public static final long[] readThisLongArrayXml(XmlPullParser parser,
+ String endTag, String[] name)
+ throws XmlPullParserException, java.io.IOException {
+
+ int num;
+ try {
+ num = Integer.parseInt(parser.getAttributeValue(null, "num"));
+ } catch (NullPointerException e) {
+ throw new XmlPullParserException("Need num attribute in long-array");
+ } catch (NumberFormatException e) {
+ throw new XmlPullParserException("Not a number in num attribute in long-array");
+ }
+ parser.next();
+
+ long[] array = new long[num];
+ int i = 0;
+
+ int eventType = parser.getEventType();
+ do {
+ if (eventType == parser.START_TAG) {
+ if (parser.getName().equals("item")) {
+ try {
+ array[i] = Long.parseLong(parser.getAttributeValue(null, "value"));
+ } catch (NullPointerException e) {
+ throw new XmlPullParserException("Need value attribute in item");
+ } catch (NumberFormatException e) {
+ throw new XmlPullParserException("Not a number in value attribute in item");
+ }
+ } else {
+ throw new XmlPullParserException("Expected item tag at: " + parser.getName());
+ }
+ } else if (eventType == parser.END_TAG) {
+ if (parser.getName().equals(endTag)) {
+ return array;
+ } else if (parser.getName().equals("item")) {
+ i++;
+ } else {
+ throw new XmlPullParserException("Expected " + endTag + " end tag at: " +
+ parser.getName());
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != parser.END_DOCUMENT);
+
+ throw new XmlPullParserException("Document ended before " + endTag + " end tag");
+ }
+
+ /**
+ * Read a double[] object from an XmlPullParser. The XML data could
+ * previously have been generated by writeDoubleArrayXml(). The XmlPullParser
+ * must be positioned <em>after</em> the tag that begins the list.
+ *
+ * @param parser The XmlPullParser from which to read the list data.
+ * @param endTag Name of the tag that will end the list, usually "double-array".
+ * @param name An array of one string, used to return the name attribute
+ * of the list's tag.
+ *
+ * @return Returns a newly generated double[].
+ *
+ * @see #readListXml
+ */
+ public static final double[] readThisDoubleArrayXml(XmlPullParser parser, String endTag,
+ String[] name) throws XmlPullParserException, java.io.IOException {
+
+ int num;
+ try {
+ num = Integer.parseInt(parser.getAttributeValue(null, "num"));
+ } catch (NullPointerException e) {
+ throw new XmlPullParserException("Need num attribute in double-array");
+ } catch (NumberFormatException e) {
+ throw new XmlPullParserException("Not a number in num attribute in double-array");
+ }
+ parser.next();
+
+ double[] array = new double[num];
+ int i = 0;
+
+ int eventType = parser.getEventType();
+ do {
+ if (eventType == parser.START_TAG) {
+ if (parser.getName().equals("item")) {
+ try {
+ array[i] = Double.parseDouble(parser.getAttributeValue(null, "value"));
+ } catch (NullPointerException e) {
+ throw new XmlPullParserException("Need value attribute in item");
+ } catch (NumberFormatException e) {
+ throw new XmlPullParserException("Not a number in value attribute in item");
+ }
+ } else {
+ throw new XmlPullParserException("Expected item tag at: " + parser.getName());
+ }
+ } else if (eventType == parser.END_TAG) {
+ if (parser.getName().equals(endTag)) {
+ return array;
+ } else if (parser.getName().equals("item")) {
+ i++;
+ } else {
+ throw new XmlPullParserException("Expected " + endTag + " end tag at: " +
+ parser.getName());
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != parser.END_DOCUMENT);
+
+ throw new XmlPullParserException("Document ended before " + endTag + " end tag");
+ }
+
+ /**
+ * Read a String[] object from an XmlPullParser. The XML data could
+ * previously have been generated by writeStringArrayXml(). The XmlPullParser
+ * must be positioned <em>after</em> the tag that begins the list.
+ *
+ * @param parser The XmlPullParser from which to read the list data.
+ * @param endTag Name of the tag that will end the list, usually "string-array".
+ * @param name An array of one string, used to return the name attribute
+ * of the list's tag.
+ *
+ * @return Returns a newly generated String[].
+ *
+ * @see #readListXml
+ */
+ public static final String[] readThisStringArrayXml(XmlPullParser parser, String endTag,
+ String[] name) throws XmlPullParserException, java.io.IOException {
+
+ int num;
+ try {
+ num = Integer.parseInt(parser.getAttributeValue(null, "num"));
+ } catch (NullPointerException e) {
+ throw new XmlPullParserException("Need num attribute in string-array");
+ } catch (NumberFormatException e) {
+ throw new XmlPullParserException("Not a number in num attribute in string-array");
+ }
+ parser.next();
+
+ String[] array = new String[num];
+ int i = 0;
+
+ int eventType = parser.getEventType();
+ do {
+ if (eventType == parser.START_TAG) {
+ if (parser.getName().equals("item")) {
+ try {
+ array[i] = parser.getAttributeValue(null, "value");
+ } catch (NullPointerException e) {
+ throw new XmlPullParserException("Need value attribute in item");
+ } catch (NumberFormatException e) {
+ throw new XmlPullParserException("Not a number in value attribute in item");
+ }
+ } else {
+ throw new XmlPullParserException("Expected item tag at: " + parser.getName());
+ }
+ } else if (eventType == parser.END_TAG) {
+ if (parser.getName().equals(endTag)) {
+ return array;
+ } else if (parser.getName().equals("item")) {
+ i++;
+ } else {
+ throw new XmlPullParserException("Expected " + endTag + " end tag at: " +
+ parser.getName());
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != parser.END_DOCUMENT);
+
+ throw new XmlPullParserException("Document ended before " + endTag + " end tag");
+ }
+
+ /**
* Read a flattened object from an XmlPullParser. The XML data could
* previously have been written with writeMapXml(), writeListXml(), or
* writeValueXml(). The XmlPullParser must be positioned <em>at</em> the
@@ -743,7 +1184,7 @@ public class XmlUtils {
int eventType = parser.getEventType();
do {
if (eventType == parser.START_TAG) {
- return readThisValueXml(parser, name);
+ return readThisValueXml(parser, name, null);
} else if (eventType == parser.END_TAG) {
throw new XmlPullParserException(
"Unexpected end tag at: " + parser.getName());
@@ -758,9 +1199,8 @@ public class XmlUtils {
"Unexpected end of document");
}
- private static final Object readThisValueXml(XmlPullParser parser, String[] name)
- throws XmlPullParserException, java.io.IOException
- {
+ private static final Object readThisValueXml(XmlPullParser parser, String[] name,
+ ReadMapCallback callback) throws XmlPullParserException, java.io.IOException {
final String valueName = parser.getAttributeValue(null, "name");
final String tagName = parser.getName();
@@ -794,11 +1234,25 @@ public class XmlUtils {
} else if ((res = readThisPrimitiveValueXml(parser, tagName)) != null) {
// all work already done by readThisPrimitiveValueXml
} else if (tagName.equals("int-array")) {
- parser.next();
res = readThisIntArrayXml(parser, "int-array", name);
name[0] = valueName;
//System.out.println("Returning value for " + valueName + ": " + res);
return res;
+ } else if (tagName.equals("long-array")) {
+ res = readThisLongArrayXml(parser, "long-array", name);
+ name[0] = valueName;
+ //System.out.println("Returning value for " + valueName + ": " + res);
+ return res;
+ } else if (tagName.equals("double-array")) {
+ res = readThisDoubleArrayXml(parser, "double-array", name);
+ name[0] = valueName;
+ //System.out.println("Returning value for " + valueName + ": " + res);
+ return res;
+ } else if (tagName.equals("string-array")) {
+ res = readThisStringArrayXml(parser, "string-array", name);
+ name[0] = valueName;
+ //System.out.println("Returning value for " + valueName + ": " + res);
+ return res;
} else if (tagName.equals("map")) {
parser.next();
res = readThisMapXml(parser, "map", name);
@@ -817,9 +1271,12 @@ public class XmlUtils {
name[0] = valueName;
//System.out.println("Returning value for " + valueName + ": " + res);
return res;
+ } else if (callback != null) {
+ res = callback.readThisUnknownObjectXml(parser, tagName);
+ name[0] = valueName;
+ return res;
} else {
- throw new XmlPullParserException(
- "Unknown tag: " + tagName);
+ throw new XmlPullParserException("Unknown tag: " + tagName);
}
// Skip through to end tag.
@@ -912,6 +1369,15 @@ public class XmlUtils {
}
}
+ public static int readIntAttribute(XmlPullParser in, String name, int defaultValue) {
+ final String value = in.getAttributeValue(null, name);
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
public static int readIntAttribute(XmlPullParser in, String name) throws IOException {
final String value = in.getAttributeValue(null, name);
try {
@@ -958,4 +1424,39 @@ public class XmlUtils {
throws IOException {
out.attribute(null, name, Boolean.toString(value));
}
+
+ /** @hide */
+ public interface WriteMapCallback {
+ /**
+ * Called from writeMapXml when an Object type is not recognized. The implementer
+ * must write out the entire element including start and end tags.
+ *
+ * @param v The object to be written out
+ * @param name The mapping key for v. Must be written into the "name" attribute of the
+ * start tag.
+ * @param out The XML output stream.
+ * @throws XmlPullParserException on unrecognized Object type.
+ * @throws IOException on XmlSerializer serialization errors.
+ * @hide
+ */
+ public void writeUnknownObject(Object v, String name, XmlSerializer out)
+ throws XmlPullParserException, IOException;
+ }
+
+ /** @hide */
+ public interface ReadMapCallback {
+ /**
+ * Called from readThisMapXml when a START_TAG is not recognized. The input stream
+ * is positioned within the start tag so that attributes can be read using in.getAttribute.
+ *
+ * @param in the XML input stream
+ * @param tag the START_TAG that was not recognized.
+ * @return the Object parsed from the stream which will be put into the map.
+ * @throws XmlPullParserException if the START_TAG is not recognized.
+ * @throws IOException on XmlPullParser serialization errors.
+ * @hide
+ */
+ public Object readThisUnknownObjectXml(XmlPullParser in, String tag)
+ throws XmlPullParserException, IOException;
+ }
}
diff --git a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
new file mode 100644
index 0000000..06838c9
--- /dev/null
+++ b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
@@ -0,0 +1,75 @@
+/*
+ * 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.view.animation;
+
+import android.animation.TimeInterpolator;
+import android.util.TimeUtils;
+import android.view.Choreographer;
+
+/**
+ * Interpolator that builds a lookup table to use. This is a fallback for
+ * building a native interpolator from a TimeInterpolator that is not marked
+ * with {@link HasNativeInterpolator}
+ *
+ * This implements TimeInterpolator to allow for easier interop with Animators
+ */
+@HasNativeInterpolator
+public class FallbackLUTInterpolator implements NativeInterpolatorFactory, TimeInterpolator {
+
+ private TimeInterpolator mSourceInterpolator;
+ private final float mLut[];
+
+ /**
+ * Used to cache the float[] LUT for use across multiple native
+ * interpolator creation
+ */
+ public FallbackLUTInterpolator(TimeInterpolator interpolator, long duration) {
+ mSourceInterpolator = interpolator;
+ mLut = createLUT(interpolator, duration);
+ }
+
+ private static float[] createLUT(TimeInterpolator interpolator, long duration) {
+ long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos();
+ int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS);
+ int numAnimFrames = (int) Math.ceil(duration / animIntervalMs);
+ float values[] = new float[numAnimFrames];
+ float lastFrame = numAnimFrames - 1;
+ for (int i = 0; i < numAnimFrames; i++) {
+ float inValue = i / lastFrame;
+ values[i] = interpolator.getInterpolation(inValue);
+ }
+ return values;
+ }
+
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createLutInterpolator(mLut);
+ }
+
+ /**
+ * Used to create a one-shot float[] LUT & native interpolator
+ */
+ public static long createNativeInterpolator(TimeInterpolator interpolator, long duration) {
+ float[] lut = createLUT(interpolator, duration);
+ return NativeInterpolatorFactoryHelper.createLutInterpolator(lut);
+ }
+
+ @Override
+ public float getInterpolation(float input) {
+ return mSourceInterpolator.getInterpolation(input);
+ }
+}
diff --git a/core/java/com/android/internal/view/animation/HasNativeInterpolator.java b/core/java/com/android/internal/view/animation/HasNativeInterpolator.java
new file mode 100644
index 0000000..48ea4da
--- /dev/null
+++ b/core/java/com/android/internal/view/animation/HasNativeInterpolator.java
@@ -0,0 +1,37 @@
+/*
+ * 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.view.animation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This is a class annotation that signals that it is safe to create
+ * a native interpolator counterpart via {@link NativeInterpolatorFactory}
+ *
+ * The idea here is to prevent subclasses of interpolators from being treated as a
+ * NativeInterpolatorFactory, and instead have them fall back to the LUT & LERP
+ * method like a custom interpolator.
+ *
+ * @hide
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface HasNativeInterpolator {
+}
diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java b/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
new file mode 100644
index 0000000..fcacd52
--- /dev/null
+++ b/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
@@ -0,0 +1,21 @@
+/*
+ * 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.view.animation;
+
+public interface NativeInterpolatorFactory {
+ long createNativeInterpolator();
+}
diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactoryHelper.java b/core/java/com/android/internal/view/animation/NativeInterpolatorFactoryHelper.java
new file mode 100644
index 0000000..7cd75f3
--- /dev/null
+++ b/core/java/com/android/internal/view/animation/NativeInterpolatorFactoryHelper.java
@@ -0,0 +1,36 @@
+/*
+ * 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.view.animation;
+
+/**
+ * Static utility class for constructing native interpolators to keep the
+ * JNI simpler
+ */
+public final class NativeInterpolatorFactoryHelper {
+ private NativeInterpolatorFactoryHelper() {}
+
+ public static native long createAccelerateDecelerateInterpolator();
+ public static native long createAccelerateInterpolator(float factor);
+ public static native long createAnticipateInterpolator(float tension);
+ public static native long createAnticipateOvershootInterpolator(float tension);
+ public static native long createBounceInterpolator();
+ public static native long createCycleInterpolator(float cycles);
+ public static native long createDecelerateInterpolator(float factor);
+ public static native long createLinearInterpolator();
+ public static native long createOvershootInterpolator(float tension);
+ public static native long createLutInterpolator(float[] values);
+}
diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java
index 183478f..9e7ff93 100644
--- a/core/java/com/android/internal/widget/AbsActionBarView.java
+++ b/core/java/com/android/internal/widget/AbsActionBarView.java
@@ -34,7 +34,7 @@ import android.view.animation.DecelerateInterpolator;
public abstract class AbsActionBarView extends ViewGroup {
protected ActionMenuView mMenuView;
protected ActionMenuPresenter mActionMenuPresenter;
- protected ActionBarContainer mSplitView;
+ protected ViewGroup mSplitView;
protected boolean mSplitActionBar;
protected boolean mSplitWhenNarrow;
protected int mContentHeight;
@@ -74,7 +74,7 @@ public abstract class AbsActionBarView extends ViewGroup {
setContentHeight(a.getLayoutDimension(R.styleable.ActionBar_height, 0));
a.recycle();
if (mSplitWhenNarrow) {
- setSplitActionBar(getContext().getResources().getBoolean(
+ setSplitToolbar(getContext().getResources().getBoolean(
com.android.internal.R.bool.split_action_bar_is_narrow));
}
if (mActionMenuPresenter != null) {
@@ -86,7 +86,7 @@ public abstract class AbsActionBarView extends ViewGroup {
* Sets whether the bar should be split right now, no questions asked.
* @param split true if the bar should split
*/
- public void setSplitActionBar(boolean split) {
+ public void setSplitToolbar(boolean split) {
mSplitActionBar = split;
}
@@ -107,7 +107,7 @@ public abstract class AbsActionBarView extends ViewGroup {
return mContentHeight;
}
- public void setSplitView(ActionBarContainer splitView) {
+ public void setSplitView(ViewGroup splitView) {
mSplitView = splitView;
}
@@ -214,6 +214,10 @@ public abstract class AbsActionBarView extends ViewGroup {
return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved();
}
+ public boolean canShowOverflowMenu() {
+ return isOverflowReserved() && getVisibility() == VISIBLE;
+ }
+
public void dismissPopupMenus() {
if (mActionMenuPresenter != null) {
mActionMenuPresenter.dismissPopupMenus();
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index ed07514..790b611 100644
--- a/core/java/com/android/internal/widget/ActionBarContainer.java
+++ b/core/java/com/android/internal/widget/ActionBarContainer.java
@@ -36,7 +36,7 @@ import android.widget.FrameLayout;
public class ActionBarContainer extends FrameLayout {
private boolean mIsTransitioning;
private View mTabContainer;
- private ActionBarView mActionBarView;
+ private View mActionBarView;
private Drawable mBackground;
private Drawable mStackedBackground;
@@ -76,7 +76,7 @@ public class ActionBarContainer extends FrameLayout {
@Override
public void onFinishInflate() {
super.onFinishInflate();
- mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
+ mActionBarView = findViewById(com.android.internal.R.id.action_bar);
}
public void setPrimaryBackground(Drawable bg) {
@@ -251,6 +251,10 @@ public class ActionBarContainer extends FrameLayout {
return null;
}
+ private boolean isCollapsed(View view) {
+ return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0;
+ }
+
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mActionBarView == null &&
@@ -263,7 +267,7 @@ public class ActionBarContainer extends FrameLayout {
if (mActionBarView == null) return;
final LayoutParams lp = (LayoutParams) mActionBarView.getLayoutParams();
- final int actionBarViewHeight = mActionBarView.isCollapsed() ? 0 :
+ final int actionBarViewHeight = isCollapsed(mActionBarView) ? 0 :
mActionBarView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
@@ -298,9 +302,8 @@ public class ActionBarContainer extends FrameLayout {
}
} else {
if (mBackground != null) {
- final ActionBarView actionBarView = mActionBarView;
- mBackground.setBounds(actionBarView.getLeft(), actionBarView.getTop(),
- actionBarView.getRight(), actionBarView.getBottom());
+ mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
+ mActionBarView.getRight(), mActionBarView.getBottom());
needsInvalidate = true;
}
mIsStacked = hasTabs;
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index e10070f..6ff77a0 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -83,7 +83,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.ActionMode, defStyleAttr, defStyleRes);
- setBackgroundDrawable(a.getDrawable(
+ setBackground(a.getDrawable(
com.android.internal.R.styleable.ActionMode_background));
mTitleStyleRes = a.getResourceId(
com.android.internal.R.styleable.ActionMode_titleTextStyle, 0);
@@ -109,7 +109,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi
}
@Override
- public void setSplitActionBar(boolean split) {
+ public void setSplitToolbar(boolean split) {
if (mSplitActionBar != split) {
if (mActionMenuPresenter != null) {
// Mode is already active; move everything over and adjust the menu itself.
@@ -137,7 +137,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi
mSplitView.addView(mMenuView, layoutParams);
}
}
- super.setSplitActionBar(split);
+ super.setSplitToolbar(split);
}
}
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index 19d58bf..8a9cb22 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -19,26 +19,35 @@ package com.android.internal.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.IntProperty;
+import android.util.Log;
import android.util.Property;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
+import android.view.Window;
import android.view.WindowInsets;
import android.widget.OverScroller;
+import android.widget.Toolbar;
+import com.android.internal.view.menu.MenuPresenter;
/**
* Special layout for the containing of an overlay action bar (and its
* content) to correctly handle fitting system windows when the content
* has request that its layout ignore them.
*/
-public class ActionBarOverlayLayout extends ViewGroup {
+public class ActionBarOverlayLayout extends ViewGroup implements DecorContentParent {
private static final String TAG = "ActionBarOverlayLayout";
private int mActionBarHeight;
@@ -47,11 +56,11 @@ public class ActionBarOverlayLayout extends ViewGroup {
// The main UI elements that we handle the layout of.
private View mContent;
- private View mActionBarBottom;
+ private ActionBarContainer mActionBarBottom;
private ActionBarContainer mActionBarTop;
// Some interior UI elements.
- private ActionBarView mActionBarView;
+ private DecorToolbar mDecorToolbar;
// Content overlay drawable - generally the action bar's shadow
private Drawable mWindowContentOverlay;
@@ -393,7 +402,7 @@ public class ActionBarOverlayLayout extends ViewGroup {
topInset = mActionBarTop.getMeasuredHeight();
}
- if (mActionBarView.isSplitActionBar()) {
+ if (mDecorToolbar.isSplit()) {
// If action bar is split, adjust bottom insets for it.
if (mActionBarBottom != null) {
if (stable) {
@@ -555,8 +564,20 @@ public class ActionBarOverlayLayout extends ViewGroup {
mContent = findViewById(com.android.internal.R.id.content);
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);
+ mDecorToolbar = getDecorToolbar(findViewById(com.android.internal.R.id.action_bar));
+ mActionBarBottom = (ActionBarContainer) findViewById(
+ com.android.internal.R.id.split_action_bar);
+ }
+ }
+
+ private DecorToolbar getDecorToolbar(View view) {
+ if (view instanceof DecorToolbar) {
+ return (DecorToolbar) view;
+ } else if (view instanceof Toolbar) {
+ return ((Toolbar) view).getWrapper();
+ } else {
+ throw new IllegalStateException("Can't make a decor toolbar out of " +
+ view.getClass().getSimpleName());
}
}
@@ -629,6 +650,179 @@ public class ActionBarOverlayLayout extends ViewGroup {
return finalY > mActionBarTop.getHeight();
}
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (super.dispatchKeyEvent(event)) {
+ return true;
+ }
+
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ final int action = event.getAction();
+
+ // Collapse any expanded action views.
+ if (mDecorToolbar != null && mDecorToolbar.hasExpandedActionView()) {
+ if (action == KeyEvent.ACTION_UP) {
+ mDecorToolbar.collapseActionView();
+ }
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void setWindowCallback(Window.Callback cb) {
+ pullChildren();
+ mDecorToolbar.setWindowCallback(cb);
+ }
+
+ @Override
+ public void setWindowTitle(CharSequence title) {
+ pullChildren();
+ mDecorToolbar.setWindowTitle(title);
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ pullChildren();
+ return mDecorToolbar.getTitle();
+ }
+
+ @Override
+ public void initFeature(int windowFeature) {
+ pullChildren();
+ switch (windowFeature) {
+ case Window.FEATURE_PROGRESS:
+ mDecorToolbar.initProgress();
+ break;
+ case Window.FEATURE_INDETERMINATE_PROGRESS:
+ mDecorToolbar.initIndeterminateProgress();
+ break;
+ case Window.FEATURE_ACTION_BAR_OVERLAY:
+ setOverlayMode(true);
+ break;
+ }
+ }
+
+ @Override
+ public void setUiOptions(int uiOptions) {
+ boolean splitActionBar = false;
+ final boolean splitWhenNarrow =
+ (uiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0;
+ if (splitWhenNarrow) {
+ splitActionBar = getContext().getResources().getBoolean(
+ com.android.internal.R.bool.split_action_bar_is_narrow);
+ }
+ if (splitActionBar) {
+ pullChildren();
+ if (mActionBarBottom != null && mDecorToolbar.canSplit()) {
+ mDecorToolbar.setSplitView(mActionBarBottom);
+ mDecorToolbar.setSplitToolbar(splitActionBar);
+ mDecorToolbar.setSplitWhenNarrow(splitWhenNarrow);
+
+ final ActionBarContextView cab = (ActionBarContextView) findViewById(
+ com.android.internal.R.id.action_context_bar);
+ cab.setSplitView(mActionBarBottom);
+ cab.setSplitToolbar(splitActionBar);
+ cab.setSplitWhenNarrow(splitWhenNarrow);
+ } else if (splitActionBar) {
+ Log.e(TAG, "Requested split action bar with " +
+ "incompatible window decor! Ignoring request.");
+ }
+ }
+ }
+
+ @Override
+ public boolean hasIcon() {
+ pullChildren();
+ return mDecorToolbar.hasIcon();
+ }
+
+ @Override
+ public boolean hasLogo() {
+ pullChildren();
+ return mDecorToolbar.hasLogo();
+ }
+
+ @Override
+ public void setIcon(int resId) {
+ pullChildren();
+ mDecorToolbar.setIcon(resId);
+ }
+
+ @Override
+ public void setIcon(Drawable d) {
+ pullChildren();
+ mDecorToolbar.setIcon(d);
+ }
+
+ @Override
+ public void setLogo(int resId) {
+ pullChildren();
+ mDecorToolbar.setLogo(resId);
+ }
+
+ @Override
+ public boolean canShowOverflowMenu() {
+ pullChildren();
+ return mDecorToolbar.canShowOverflowMenu();
+ }
+
+ @Override
+ public boolean isOverflowMenuShowing() {
+ pullChildren();
+ return mDecorToolbar.isOverflowMenuShowing();
+ }
+
+ @Override
+ public boolean isOverflowMenuShowPending() {
+ pullChildren();
+ return mDecorToolbar.isOverflowMenuShowPending();
+ }
+
+ @Override
+ public boolean showOverflowMenu() {
+ pullChildren();
+ return mDecorToolbar.showOverflowMenu();
+ }
+
+ @Override
+ public boolean hideOverflowMenu() {
+ pullChildren();
+ return mDecorToolbar.hideOverflowMenu();
+ }
+
+ @Override
+ public void setMenuPrepared() {
+ pullChildren();
+ mDecorToolbar.setMenuPrepared();
+ }
+
+ @Override
+ public void setMenu(Menu menu, MenuPresenter.Callback cb) {
+ pullChildren();
+ mDecorToolbar.setMenu(menu, cb);
+ }
+
+ @Override
+ public void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) {
+ pullChildren();
+ mDecorToolbar.saveHierarchyState(toolbarStates);
+ }
+
+ @Override
+ public void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) {
+ pullChildren();
+ mDecorToolbar.restoreHierarchyState(toolbarStates);
+ }
+
+ @Override
+ public void dismissPopups() {
+ pullChildren();
+ mDecorToolbar.dismissPopupMenus();
+ }
+
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 60631b9..af82778 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -18,7 +18,6 @@ package com.android.internal.widget;
import android.animation.LayoutTransition;
import android.app.ActionBar;
-import android.app.ActionBar.OnNavigationListener;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -63,7 +62,7 @@ import com.android.internal.view.menu.SubMenuBuilder;
/**
* @hide
*/
-public class ActionBarView extends AbsActionBarView {
+public class ActionBarView extends AbsActionBarView implements DecorToolbar {
private static final String TAG = "ActionBarView";
/**
@@ -117,8 +116,7 @@ public class ActionBarView extends AbsActionBarView {
private boolean mUserTitle;
private boolean mIncludeTabs;
- private boolean mIsCollapsable;
- private boolean mIsCollapsed;
+ private boolean mIsCollapsible;
private boolean mWasHomeEnabled; // Was it enabled before action view expansion?
private MenuBuilder mOptionsMenu;
@@ -129,7 +127,7 @@ public class ActionBarView extends AbsActionBarView {
private ActionMenuItem mLogoNavItem;
private SpinnerAdapter mSpinnerAdapter;
- private OnNavigationListener mCallback;
+ private AdapterView.OnItemSelectedListener mNavItemSelectedListener;
private Runnable mTabSelector;
@@ -138,18 +136,6 @@ public class ActionBarView extends AbsActionBarView {
Window.Callback mWindowCallback;
- private final AdapterView.OnItemSelectedListener mNavItemSelectedListener =
- new AdapterView.OnItemSelectedListener() {
- public void onItemSelected(AdapterView parent, View view, int position, long id) {
- if (mCallback != null) {
- mCallback.onNavigationItemSelected(position, id);
- }
- }
- public void onNothingSelected(AdapterView parent) {
- // Do nothing
- }
- };
-
private final OnClickListener mExpandedActionViewUpListener = new OnClickListener() {
@Override
public void onClick(View v) {
@@ -178,8 +164,6 @@ public class ActionBarView extends AbsActionBarView {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar,
com.android.internal.R.attr.actionBarStyle, 0);
- ApplicationInfo appInfo = context.getApplicationInfo();
- PackageManager pm = context.getPackageManager();
mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode,
ActionBar.NAVIGATION_MODE_STANDARD);
mTitle = a.getText(R.styleable.ActionBar_title);
@@ -260,7 +244,7 @@ public class ActionBarView extends AbsActionBarView {
}
if (mHomeDescriptionRes != 0) {
- setHomeActionContentDescription(mHomeDescriptionRes);
+ setNavigationContentDescription(mHomeDescriptionRes);
}
if (mTabScrollView != null && mIncludeTabs) {
@@ -313,7 +297,7 @@ public class ActionBarView extends AbsActionBarView {
}
@Override
- public void setSplitActionBar(boolean splitActionBar) {
+ public void setSplitToolbar(boolean splitActionBar) {
if (mSplitActionBar != splitActionBar) {
if (mMenuView != null) {
final ViewGroup oldParent = (ViewGroup) mMenuView.getParent();
@@ -349,18 +333,26 @@ public class ActionBarView extends AbsActionBarView {
mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
}
}
- super.setSplitActionBar(splitActionBar);
+ super.setSplitToolbar(splitActionBar);
}
}
- public boolean isSplitActionBar() {
+ public boolean isSplit() {
return mSplitActionBar;
}
+ public boolean canSplit() {
+ return true;
+ }
+
public boolean hasEmbeddedTabs() {
return mIncludeTabs;
}
+ public void setEmbeddedTabView(View view) {
+ setEmbeddedTabView((ScrollingTabContainerView) view);
+ }
+
public void setEmbeddedTabView(ScrollingTabContainerView tabs) {
if (mTabScrollView != null) {
removeView(mTabScrollView);
@@ -376,10 +368,6 @@ public class ActionBarView extends AbsActionBarView {
}
}
- public void setCallback(OnNavigationListener callback) {
- mCallback = callback;
- }
-
public void setMenuPrepared() {
mMenuPrepared = true;
}
@@ -473,7 +461,7 @@ public class ActionBarView extends AbsActionBarView {
}
}
- public void setCustomNavigationView(View view) {
+ public void setCustomView(View view) {
final boolean showCustom = (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0;
if (showCustom) {
ActionBarTransition.beginDelayedTransition(this);
@@ -765,15 +753,16 @@ public class ActionBarView extends AbsActionBarView {
}
}
- public void setDropdownAdapter(SpinnerAdapter adapter) {
+ public void setDropdownParams(SpinnerAdapter adapter, AdapterView.OnItemSelectedListener l) {
mSpinnerAdapter = adapter;
+ mNavItemSelectedListener = l;
if (mSpinner != null) {
mSpinner.setAdapter(adapter);
}
}
- public SpinnerAdapter getDropdownAdapter() {
- return mSpinnerAdapter;
+ public int getDropdownItemCount() {
+ return mSpinnerAdapter != null ? mSpinnerAdapter.getCount() : 0;
}
public void setDropdownSelectedPosition(int position) {
@@ -784,7 +773,7 @@ public class ActionBarView extends AbsActionBarView {
return mSpinner.getSelectedItemPosition();
}
- public View getCustomNavigationView() {
+ public View getCustomView() {
return mCustomNavView;
}
@@ -797,6 +786,11 @@ public class ActionBarView extends AbsActionBarView {
}
@Override
+ public ViewGroup getViewGroup() {
+ return this;
+ }
+
+ @Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
// Used by custom nav views if they don't supply layout params. Everything else
// added to an ActionBarView should have them already.
@@ -860,12 +854,8 @@ public class ActionBarView extends AbsActionBarView {
mContextView = view;
}
- public void setCollapsable(boolean collapsable) {
- mIsCollapsable = collapsable;
- }
-
- public boolean isCollapsed() {
- return mIsCollapsed;
+ public void setCollapsible(boolean collapsible) {
+ mIsCollapsible = collapsible;
}
/**
@@ -893,7 +883,7 @@ public class ActionBarView extends AbsActionBarView {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int childCount = getChildCount();
- if (mIsCollapsable) {
+ if (mIsCollapsible) {
int visibleChildren = 0;
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
@@ -915,11 +905,9 @@ public class ActionBarView extends AbsActionBarView {
if (visibleChildren == 0) {
// No size for an empty action bar when collapsable.
setMeasuredDimension(0, 0);
- mIsCollapsed = true;
return;
}
}
- mIsCollapsed = false;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
@@ -1323,20 +1311,20 @@ public class ActionBarView extends AbsActionBarView {
}
}
- public void setHomeAsUpIndicator(Drawable indicator) {
+ public void setNavigationIcon(Drawable indicator) {
mHomeLayout.setUpIndicator(indicator);
}
- public void setHomeAsUpIndicator(int resId) {
+ public void setNavigationIcon(int resId) {
mHomeLayout.setUpIndicator(resId);
}
- public void setHomeActionContentDescription(CharSequence description) {
+ public void setNavigationContentDescription(CharSequence description) {
mHomeDescription = description;
updateHomeAccessibility(mUpGoerFive.isEnabled());
}
- public void setHomeActionContentDescription(int resId) {
+ public void setNavigationContentDescription(int resId) {
mHomeDescriptionRes = resId;
mHomeDescription = resId != 0 ? getResources().getText(resId) : null;
updateHomeAccessibility(mUpGoerFive.isEnabled());
diff --git a/core/java/com/android/internal/widget/DecorContentParent.java b/core/java/com/android/internal/widget/DecorContentParent.java
new file mode 100644
index 0000000..4fa370a
--- /dev/null
+++ b/core/java/com/android/internal/widget/DecorContentParent.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.internal.widget;
+
+import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
+import android.util.SparseArray;
+import android.view.Menu;
+import android.view.Window;
+import com.android.internal.view.menu.MenuPresenter;
+
+/**
+ * Implemented by the top-level decor layout for a window. DecorContentParent offers
+ * entry points for a number of title/window decor features.
+ */
+public interface DecorContentParent {
+ void setWindowCallback(Window.Callback cb);
+ void setWindowTitle(CharSequence title);
+ CharSequence getTitle();
+ void initFeature(int windowFeature);
+ void setUiOptions(int uiOptions);
+ boolean hasIcon();
+ boolean hasLogo();
+ void setIcon(int resId);
+ void setIcon(Drawable d);
+ void setLogo(int resId);
+ boolean canShowOverflowMenu();
+ boolean isOverflowMenuShowing();
+ boolean isOverflowMenuShowPending();
+ boolean showOverflowMenu();
+ boolean hideOverflowMenu();
+ void setMenuPrepared();
+ void setMenu(Menu menu, MenuPresenter.Callback cb);
+ void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates);
+ void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates);
+ void dismissPopups();
+
+}
diff --git a/core/java/com/android/internal/widget/DecorToolbar.java b/core/java/com/android/internal/widget/DecorToolbar.java
new file mode 100644
index 0000000..ee6988e
--- /dev/null
+++ b/core/java/com/android/internal/widget/DecorToolbar.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 com.android.internal.widget;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
+import android.util.SparseArray;
+import android.view.Menu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.SpinnerAdapter;
+import com.android.internal.view.menu.MenuPresenter;
+
+/**
+ * Common interface for a toolbar that sits as part of the window decor.
+ * Layouts that control window decor use this as a point of interaction with different
+ * bar implementations.
+ *
+ * @hide
+ */
+public interface DecorToolbar {
+ ViewGroup getViewGroup();
+ Context getContext();
+ boolean isSplit();
+ boolean hasExpandedActionView();
+ void collapseActionView();
+ void setWindowCallback(Window.Callback cb);
+ void setWindowTitle(CharSequence title);
+ CharSequence getTitle();
+ void setTitle(CharSequence title);
+ CharSequence getSubtitle();
+ void setSubtitle(CharSequence subtitle);
+ void initProgress();
+ void initIndeterminateProgress();
+ boolean canSplit();
+ void setSplitView(ViewGroup splitView);
+ void setSplitToolbar(boolean split);
+ void setSplitWhenNarrow(boolean splitWhenNarrow);
+ boolean hasIcon();
+ boolean hasLogo();
+ void setIcon(int resId);
+ void setIcon(Drawable d);
+ void setLogo(int resId);
+ void setLogo(Drawable d);
+ boolean canShowOverflowMenu();
+ boolean isOverflowMenuShowing();
+ boolean isOverflowMenuShowPending();
+ boolean showOverflowMenu();
+ boolean hideOverflowMenu();
+ void setMenuPrepared();
+ void setMenu(Menu menu, MenuPresenter.Callback cb);
+ void dismissPopupMenus();
+
+ int getDisplayOptions();
+ void setDisplayOptions(int opts);
+ void setEmbeddedTabView(View tabView);
+ boolean hasEmbeddedTabs();
+ boolean isTitleTruncated();
+ void setCollapsible(boolean collapsible);
+ void setHomeButtonEnabled(boolean enable);
+ int getNavigationMode();
+ void setNavigationMode(int mode);
+ void setDropdownParams(SpinnerAdapter adapter, AdapterView.OnItemSelectedListener listener);
+ void setDropdownSelectedPosition(int position);
+ int getDropdownSelectedPosition();
+ int getDropdownItemCount();
+ void setCustomView(View view);
+ View getCustomView();
+ void animateToVisibility(int visibility);
+ void setNavigationIcon(Drawable icon);
+ void setNavigationIcon(int resId);
+ void setNavigationContentDescription(CharSequence description);
+ void setNavigationContentDescription(int resId);
+ void saveHierarchyState(SparseArray<Parcelable> toolbarStates);
+ void restoreHierarchyState(SparseArray<Parcelable> toolbarStates);
+}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 9501f92..c70841b 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -16,6 +16,8 @@
package com.android.internal.widget;
+import com.android.internal.widget.ILockSettingsObserver;
+
/** {@hide} */
interface ILockSettings {
void setBoolean(in String key, in boolean value, in int userId);
@@ -32,4 +34,6 @@ interface ILockSettings {
boolean havePattern(int userId);
boolean havePassword(int userId);
void removeUser(int userId);
+ void registerObserver(in ILockSettingsObserver observer);
+ void unregisterObserver(in ILockSettingsObserver observer);
}
diff --git a/core/java/com/android/internal/widget/ILockSettingsObserver.aidl b/core/java/com/android/internal/widget/ILockSettingsObserver.aidl
new file mode 100644
index 0000000..6c354d8
--- /dev/null
+++ b/core/java/com/android/internal/widget/ILockSettingsObserver.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.widget;
+
+/** {@hide} */
+oneway interface ILockSettingsObserver {
+ void onLockSettingChanged(in String key, in int userId);
+}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 2882b54..d31c5cc 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -43,7 +43,6 @@ import android.view.View;
import android.widget.Button;
import com.android.internal.R;
-import com.android.internal.telephony.ITelephony;
import com.google.android.collect.Lists;
import java.security.MessageDigest;
@@ -199,8 +198,8 @@ public class LockPatternUtils {
private ILockSettings getLockSettings() {
if (mLockSettingsService == null) {
- mLockSettingsService = ILockSettings.Stub.asInterface(
- (IBinder) ServiceManager.getService("lock_settings"));
+ mLockSettingsService = LockPatternUtilsCache.getInstance(
+ ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings")));
}
return mLockSettingsService;
}
@@ -1360,19 +1359,11 @@ public class LockPatternUtils {
/**
* Resumes a call in progress. Typically launched from the EmergencyCall button
* on various lockscreens.
- *
- * @return true if we were able to tell InCallScreen to show.
*/
- public boolean resumeCall() {
- ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
- try {
- if (phone != null && phone.showCallScreen()) {
- return true;
- }
- } catch (RemoteException e) {
- // What can we do?
- }
- return false;
+ public void resumeCall() {
+ TelephonyManager telephonyManager =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ telephonyManager.showCallScreen();
}
private void finishBiometricWeak() {
diff --git a/core/java/com/android/internal/widget/LockPatternUtilsCache.java b/core/java/com/android/internal/widget/LockPatternUtilsCache.java
new file mode 100644
index 0000000..624f67c
--- /dev/null
+++ b/core/java/com/android/internal/widget/LockPatternUtilsCache.java
@@ -0,0 +1,243 @@
+/*
+ * 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.widget;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+
+/**
+ * A decorator for {@link ILockSettings} that caches the key-value responses in memory.
+ *
+ * Specifically, the return values of {@link #getString(String, String, int)},
+ * {@link #getLong(String, long, int)} and {@link #getBoolean(String, boolean, int)} are cached.
+ */
+public class LockPatternUtilsCache implements ILockSettings {
+
+ private static final String HAS_LOCK_PATTERN_CACHE_KEY
+ = "LockPatternUtils.Cache.HasLockPatternCacheKey";
+ private static final String HAS_LOCK_PASSWORD_CACHE_KEY
+ = "LockPatternUtils.Cache.HasLockPasswordCacheKey";
+
+ private static LockPatternUtilsCache sInstance;
+
+ private final ILockSettings mService;
+
+ /** Only access when holding {@code mCache} lock. */
+ private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>();
+
+ /** Only access when holding {@link #mCache} lock. */
+ private final CacheKey mCacheKey = new CacheKey();
+
+
+ public static synchronized LockPatternUtilsCache getInstance(ILockSettings service) {
+ if (sInstance == null) {
+ sInstance = new LockPatternUtilsCache(service);
+ }
+ return sInstance;
+ }
+
+ // ILockSettings
+
+ private LockPatternUtilsCache(ILockSettings service) {
+ mService = service;
+ try {
+ service.registerObserver(mObserver);
+ } catch (RemoteException e) {
+ // Not safe to do caching without the observer. System process has probably died
+ // anyway, so crashing here is fine.
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void setBoolean(String key, boolean value, int userId) throws RemoteException {
+ invalidateCache(key, userId);
+ mService.setBoolean(key, value, userId);
+ putCache(key, userId, value);
+ }
+
+ public void setLong(String key, long value, int userId) throws RemoteException {
+ invalidateCache(key, userId);
+ mService.setLong(key, value, userId);
+ putCache(key, userId, value);
+ }
+
+ public void setString(String key, String value, int userId) throws RemoteException {
+ invalidateCache(key, userId);
+ mService.setString(key, value, userId);
+ putCache(key, userId, value);
+ }
+
+ public long getLong(String key, long defaultValue, int userId) throws RemoteException {
+ Object value = peekCache(key, userId);
+ if (value instanceof Long) {
+ return (long) value;
+ }
+ long result = mService.getLong(key, defaultValue, userId);
+ putCache(key, userId, result);
+ return result;
+ }
+
+ public String getString(String key, String defaultValue, int userId) throws RemoteException {
+ Object value = peekCache(key, userId);
+ if (value instanceof String) {
+ return (String) value;
+ }
+ String result = mService.getString(key, defaultValue, userId);
+ putCache(key, userId, result);
+ return result;
+ }
+
+ public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
+ Object value = peekCache(key, userId);
+ if (value instanceof Boolean) {
+ return (boolean) value;
+ }
+ boolean result = mService.getBoolean(key, defaultValue, userId);
+ putCache(key, userId, result);
+ return result;
+ }
+
+ @Override
+ public void setLockPattern(String pattern, int userId) throws RemoteException {
+ invalidateCache(HAS_LOCK_PATTERN_CACHE_KEY, userId);
+ mService.setLockPattern(pattern, userId);
+ putCache(HAS_LOCK_PATTERN_CACHE_KEY, userId, pattern != null);
+ }
+
+ @Override
+ public boolean checkPattern(String pattern, int userId) throws RemoteException {
+ return mService.checkPattern(pattern, userId);
+ }
+
+ @Override
+ public void setLockPassword(String password, int userId) throws RemoteException {
+ invalidateCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId);
+ mService.setLockPassword(password, userId);
+ putCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId, password != null);
+ }
+
+ @Override
+ public boolean checkPassword(String password, int userId) throws RemoteException {
+ return mService.checkPassword(password, userId);
+ }
+
+ @Override
+ public boolean checkVoldPassword(int userId) throws RemoteException {
+ return mService.checkVoldPassword(userId);
+ }
+
+ @Override
+ public boolean havePattern(int userId) throws RemoteException {
+ Object value = peekCache(HAS_LOCK_PATTERN_CACHE_KEY, userId);
+ if (value instanceof Boolean) {
+ return (boolean) value;
+ }
+ boolean result = mService.havePattern(userId);
+ putCache(HAS_LOCK_PATTERN_CACHE_KEY, userId, result);
+ return result;
+ }
+
+ @Override
+ public boolean havePassword(int userId) throws RemoteException {
+ Object value = peekCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId);
+ if (value instanceof Boolean) {
+ return (boolean) value;
+ }
+ boolean result = mService.havePassword(userId);
+ putCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId, result);
+ return result;
+ }
+
+ @Override
+ public void removeUser(int userId) throws RemoteException {
+ mService.removeUser(userId);
+ }
+
+ @Override
+ public void registerObserver(ILockSettingsObserver observer) throws RemoteException {
+ mService.registerObserver(observer);
+ }
+
+ @Override
+ public void unregisterObserver(ILockSettingsObserver observer) throws RemoteException {
+ mService.unregisterObserver(observer);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return mService.asBinder();
+ }
+
+ // Caching
+
+ private Object peekCache(String key, int userId) {
+ synchronized (mCache) {
+ // Safe to reuse mCacheKey, because it is not stored in the map.
+ return mCache.get(mCacheKey.set(key, userId));
+ }
+ }
+
+ private void putCache(String key, int userId, Object value) {
+ synchronized (mCache) {
+ // Create a new key, because this will be stored in the map.
+ mCache.put(new CacheKey().set(key, userId), value);
+ }
+ }
+
+ private void invalidateCache(String key, int userId) {
+ synchronized (mCache) {
+ // Safe to reuse mCacheKey, because it is not stored in the map.
+ mCache.remove(mCacheKey.set(key, userId));
+ }
+ }
+
+ private final ILockSettingsObserver mObserver = new ILockSettingsObserver.Stub() {
+ @Override
+ public void onLockSettingChanged(String key, int userId) throws RemoteException {
+ invalidateCache(key, userId);
+ }
+ };
+
+ private static final class CacheKey {
+ String key;
+ int userId;
+
+ public CacheKey set(String key, int userId) {
+ this.key = key;
+ this.userId = userId;
+ return this;
+ }
+
+ public CacheKey copy() {
+ return new CacheKey().set(key, userId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof CacheKey))
+ return false;
+ CacheKey o = (CacheKey) obj;
+ return userId == o.userId && key.equals(o.key);
+ }
+
+ @Override
+ public int hashCode() {
+ return key.hashCode() ^ userId;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 36ed344..d841d53 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -57,6 +57,7 @@ public class LockPatternView extends View {
private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h)
private static final boolean PROFILE_DRAWING = false;
+ private final CellState[][] mCellStates;
private boolean mDrawingProfilingStarted = false;
private Paint mPaint = new Paint();
@@ -187,6 +188,12 @@ public class LockPatternView extends View {
}
}
+ public static class CellState {
+ public float scale = 1.0f;
+ public float translateY = 0.0f;
+ public float alpha = 1.0f;
+ }
+
/**
* How to display the current pattern.
*/
@@ -296,6 +303,18 @@ public class LockPatternView extends View {
mBitmapHeight = Math.max(mBitmapHeight, bitmap.getHeight());
}
+ mPaint.setFilterBitmap(true);
+
+ mCellStates = new CellState[3][3];
+ for (int i = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ mCellStates[i][j] = new CellState();
+ }
+ }
+ }
+
+ public CellState[][] getCellStates() {
+ return mCellStates;
}
private Bitmap getBitmapFor(int resId) {
@@ -873,18 +892,22 @@ public class LockPatternView extends View {
//float centerY = mPaddingTop + i * mSquareHeight + (mSquareHeight / 2);
for (int j = 0; j < 3; j++) {
float leftX = paddingLeft + j * squareWidth;
- drawCircle(canvas, (int) leftX, (int) topY, drawLookup[i][j]);
+ float scale = mCellStates[i][j].scale;
+ mPaint.setAlpha((int) (mCellStates[i][j].alpha * 255));
+ float translationY = mCellStates[i][j].translateY;
+ drawCircle(canvas, (int) leftX, (int) topY + translationY, scale, drawLookup[i][j]);
}
}
+ // Reset the alpha to draw normally
+ mPaint.setAlpha(255);
+
// 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 we are in stealth mode)
final boolean drawPath = !mInStealthMode;
// 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) {
for (int i = 0; i < count - 1; i++) {
Cell cell = pattern.get(i);
@@ -898,7 +921,8 @@ public class LockPatternView extends View {
}
float leftX = paddingLeft + cell.column * squareWidth;
- float topY = paddingTop + cell.row * squareHeight;
+ float topY = paddingTop + cell.row * squareHeight
+ + mCellStates[cell.row][cell.column].translateY;
drawArrow(canvas, leftX, topY, cell, next);
}
@@ -919,6 +943,9 @@ public class LockPatternView extends View {
float centerX = getCenterXForColumn(cell.column);
float centerY = getCenterYForRow(cell.row);
+
+ // Respect translation in animation
+ centerY += mCellStates[cell.row][cell.column].translateY;
if (i == 0) {
currentPath.moveTo(centerX, centerY);
} else {
@@ -933,8 +960,6 @@ public class LockPatternView extends View {
}
canvas.drawPath(currentPath, mPathPaint);
}
-
- mPaint.setFilterBitmap(oldFlag); // restore default flag
}
private void drawArrow(Canvas canvas, float leftX, float topY, Cell start, Cell end) {
@@ -979,7 +1004,8 @@ public class LockPatternView extends View {
* @param topY
* @param partOfPattern Whether this circle is part of the pattern.
*/
- private void drawCircle(Canvas canvas, int leftX, int topY, boolean partOfPattern) {
+ private void drawCircle(Canvas canvas, float leftX, float topY, float scale,
+ boolean partOfPattern) {
Bitmap outerCircle;
Bitmap innerCircle;
@@ -1019,7 +1045,7 @@ public class LockPatternView extends View {
mCircleMatrix.setTranslate(leftX + offsetX, topY + offsetY);
mCircleMatrix.preTranslate(mBitmapWidth/2, mBitmapHeight/2);
- mCircleMatrix.preScale(sx, sy);
+ mCircleMatrix.preScale(sx * scale, sy * scale);
mCircleMatrix.preTranslate(-mBitmapWidth/2, -mBitmapHeight/2);
canvas.drawBitmap(outerCircle, mCircleMatrix, mPaint);
diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java
index bcfa036..002573e 100644
--- a/core/java/com/android/internal/widget/SwipeDismissLayout.java
+++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java
@@ -35,7 +35,7 @@ import android.widget.FrameLayout;
public class SwipeDismissLayout extends FrameLayout {
private static final String TAG = "SwipeDismissLayout";
- private static final float DISMISS_MIN_DRAG_WIDTH_RATIO = .4f;
+ private static final float DISMISS_MIN_DRAG_WIDTH_RATIO = .33f;
public interface OnDismissedListener {
void onDismissed(SwipeDismissLayout layout);
diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
new file mode 100644
index 0000000..3e15c32
--- /dev/null
+++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
@@ -0,0 +1,541 @@
+/*
+ * 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.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.ActionBar;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.ActionMenuPresenter;
+import android.widget.AdapterView;
+import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
+import android.widget.Toolbar;
+import com.android.internal.R;
+import com.android.internal.view.menu.ActionMenuItem;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuPresenter;
+
+/**
+ * Internal class used to interact with the Toolbar widget without
+ * exposing interface methods to the public API.
+ *
+ * <p>ToolbarWidgetWrapper manages the differences between Toolbar and ActionBarView
+ * so that either variant acting as a
+ * {@link com.android.internal.app.WindowDecorActionBar WindowDecorActionBar} can behave
+ * in the same way.</p>
+ *
+ * @hide
+ */
+public class ToolbarWidgetWrapper implements DecorToolbar {
+ private static final String TAG = "ToolbarWidgetWrapper";
+
+ private static final int AFFECTS_LOGO_MASK =
+ ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_USE_LOGO;
+
+ private Toolbar mToolbar;
+
+ private int mDisplayOpts;
+ private View mTabView;
+ private Spinner mSpinner;
+ private View mCustomView;
+
+ private Drawable mIcon;
+ private Drawable mLogo;
+ private Drawable mNavIcon;
+
+ private boolean mTitleSet;
+ private CharSequence mTitle;
+ private CharSequence mSubtitle;
+
+ private Window.Callback mWindowCallback;
+ private boolean mMenuPrepared;
+ private ActionMenuPresenter mActionMenuPresenter;
+
+ public ToolbarWidgetWrapper(Toolbar toolbar) {
+ mToolbar = toolbar;
+
+ mTitle = toolbar.getTitle();
+ mSubtitle = toolbar.getSubtitle();
+ mTitleSet = !TextUtils.isEmpty(mTitle);
+
+ final TypedArray a = toolbar.getContext().obtainStyledAttributes(null,
+ R.styleable.ActionBar, R.attr.actionBarStyle, 0);
+
+ final CharSequence title = a.getText(R.styleable.ActionBar_title);
+ if (!TextUtils.isEmpty(title)) {
+ setTitle(title);
+ }
+
+ final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle);
+ if (!TextUtils.isEmpty(subtitle)) {
+ setSubtitle(subtitle);
+ }
+
+ final Drawable logo = a.getDrawable(R.styleable.ActionBar_logo);
+ if (logo != null) {
+ setLogo(logo);
+ }
+
+ final Drawable icon = a.getDrawable(R.styleable.ActionBar_icon);
+ if (icon != null) {
+ setIcon(icon);
+ }
+
+ final Drawable navIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator);
+ if (navIcon != null) {
+ setNavigationIcon(navIcon);
+ }
+
+ setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, 0));
+
+ final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0);
+ if (customNavId != 0) {
+ setCustomView(LayoutInflater.from(mToolbar.getContext()).inflate(customNavId,
+ mToolbar, false));
+ setDisplayOptions(mDisplayOpts | ActionBar.DISPLAY_SHOW_CUSTOM);
+ }
+
+ final int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
+ if (height > 0) {
+ final ViewGroup.LayoutParams lp = mToolbar.getLayoutParams();
+ lp.height = height;
+ mToolbar.setLayoutParams(lp);
+ }
+
+ final int contentInsetStart = a.getDimensionPixelOffset(
+ R.styleable.ActionBar_contentInsetStart, 0);
+ final int contentInsetEnd = a.getDimensionPixelOffset(
+ R.styleable.ActionBar_contentInsetEnd, 0);
+ if (contentInsetStart > 0 || contentInsetEnd > 0) {
+ mToolbar.setContentInsetsRelative(contentInsetStart, contentInsetEnd);
+ }
+
+ final int titleTextStyle = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
+ if (titleTextStyle != 0) {
+ mToolbar.setTitleTextAppearance(mToolbar.getContext(), titleTextStyle);
+ }
+
+ final int subtitleTextStyle = a.getResourceId(R.styleable.ActionBar_subtitleTextStyle, 0);
+ if (subtitleTextStyle != 0) {
+ mToolbar.setSubtitleTextAppearance(mToolbar.getContext(), subtitleTextStyle);
+ }
+
+ a.recycle();
+
+ mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
+ final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(),
+ 0, android.R.id.home, 0, 0, mTitle);
+ @Override
+ public void onClick(View v) {
+ if (mWindowCallback != null && mMenuPrepared) {
+ mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mNavItem);
+ }
+ }
+ });
+ }
+
+ @Override
+ public ViewGroup getViewGroup() {
+ return mToolbar;
+ }
+
+ @Override
+ public Context getContext() {
+ return mToolbar.getContext();
+ }
+
+ @Override
+ public boolean isSplit() {
+ return false;
+ }
+
+ @Override
+ public boolean hasExpandedActionView() {
+ return mToolbar.hasExpandedActionView();
+ }
+
+ @Override
+ public void collapseActionView() {
+ mToolbar.collapseActionView();
+ }
+
+ @Override
+ public void setWindowCallback(Window.Callback cb) {
+ mWindowCallback = cb;
+ }
+
+ @Override
+ public void setWindowTitle(CharSequence title) {
+ // "Real" title always trumps window title.
+ if (!mTitleSet) {
+ setTitleInt(title);
+ }
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return mToolbar.getTitle();
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ mTitleSet = true;
+ setTitleInt(title);
+ }
+
+ private void setTitleInt(CharSequence title) {
+ mTitle = title;
+ if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
+ mToolbar.setTitle(title);
+ }
+ }
+
+ @Override
+ public CharSequence getSubtitle() {
+ return mToolbar.getSubtitle();
+ }
+
+ @Override
+ public void setSubtitle(CharSequence subtitle) {
+ mSubtitle = subtitle;
+ if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
+ mToolbar.setSubtitle(subtitle);
+ }
+ }
+
+ @Override
+ public void initProgress() {
+ Log.i(TAG, "Progress display unsupported");
+ }
+
+ @Override
+ public void initIndeterminateProgress() {
+ Log.i(TAG, "Progress display unsupported");
+ }
+
+ @Override
+ public boolean canSplit() {
+ return false;
+ }
+
+ @Override
+ public void setSplitView(ViewGroup splitView) {
+ }
+
+ @Override
+ public void setSplitToolbar(boolean split) {
+ if (split) {
+ throw new UnsupportedOperationException("Cannot split an android.widget.Toolbar");
+ }
+ }
+
+ @Override
+ public void setSplitWhenNarrow(boolean splitWhenNarrow) {
+ // Ignore.
+ }
+
+ @Override
+ public boolean hasIcon() {
+ return mIcon != null;
+ }
+
+ @Override
+ public boolean hasLogo() {
+ return mLogo != null;
+ }
+
+ @Override
+ public void setIcon(int resId) {
+ setIcon(resId != 0 ? getContext().getDrawable(resId) : null);
+ }
+
+ @Override
+ public void setIcon(Drawable d) {
+ mIcon = d;
+ updateToolbarLogo();
+ }
+
+ @Override
+ public void setLogo(int resId) {
+ setLogo(resId != 0 ? getContext().getDrawable(resId) : null);
+ }
+
+ @Override
+ public void setLogo(Drawable d) {
+ mLogo = d;
+ updateToolbarLogo();
+ }
+
+ private void updateToolbarLogo() {
+ Drawable logo = null;
+ if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_HOME) != 0) {
+ if ((mDisplayOpts & ActionBar.DISPLAY_USE_LOGO) != 0) {
+ logo = mLogo != null ? mLogo : mIcon;
+ } else {
+ logo = mIcon;
+ }
+ }
+ mToolbar.setLogo(logo);
+ }
+
+ @Override
+ public boolean canShowOverflowMenu() {
+ return mToolbar.canShowOverflowMenu();
+ }
+
+ @Override
+ public boolean isOverflowMenuShowing() {
+ return mToolbar.isOverflowMenuShowing();
+ }
+
+ @Override
+ public boolean isOverflowMenuShowPending() {
+ return mToolbar.isOverflowMenuShowPending();
+ }
+
+ @Override
+ public boolean showOverflowMenu() {
+ return mToolbar.showOverflowMenu();
+ }
+
+ @Override
+ public boolean hideOverflowMenu() {
+ return mToolbar.hideOverflowMenu();
+ }
+
+ @Override
+ public void setMenuPrepared() {
+ mMenuPrepared = true;
+ }
+
+ @Override
+ public void setMenu(Menu menu, MenuPresenter.Callback cb) {
+ if (mActionMenuPresenter == null) {
+ mActionMenuPresenter = new ActionMenuPresenter(mToolbar.getContext());
+ mActionMenuPresenter.setId(com.android.internal.R.id.action_menu_presenter);
+ }
+ mActionMenuPresenter.setCallback(cb);
+ mToolbar.setMenu((MenuBuilder) menu, mActionMenuPresenter);
+ }
+
+ @Override
+ public void dismissPopupMenus() {
+ mToolbar.dismissPopupMenus();
+ }
+
+ @Override
+ public int getDisplayOptions() {
+ return mDisplayOpts;
+ }
+
+ @Override
+ public void setDisplayOptions(int newOpts) {
+ final int oldOpts = mDisplayOpts;
+ final int changed = oldOpts ^ newOpts;
+ mDisplayOpts = newOpts;
+ if (changed != 0) {
+ if ((changed & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+ if ((newOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+ mToolbar.setNavigationIcon(mNavIcon);
+ } else {
+ mToolbar.setNavigationIcon(null);
+ }
+ }
+
+ if ((changed & AFFECTS_LOGO_MASK) != 0) {
+ updateToolbarLogo();
+ }
+
+ if ((changed & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
+ if ((newOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
+ mToolbar.setTitle(mTitle);
+ mToolbar.setSubtitle(mSubtitle);
+ } else {
+ mToolbar.setTitle(null);
+ mToolbar.setSubtitle(null);
+ }
+ }
+
+ if ((changed & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomView != null) {
+ if ((newOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
+ mToolbar.addView(mCustomView);
+ } else {
+ mToolbar.removeView(mCustomView);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void setEmbeddedTabView(View tabView) {
+ mTabView = tabView;
+ }
+
+ @Override
+ public boolean hasEmbeddedTabs() {
+ return mTabView != null;
+ }
+
+ @Override
+ public boolean isTitleTruncated() {
+ return mToolbar.isTitleTruncated();
+ }
+
+ @Override
+ public void setCollapsible(boolean collapsible) {
+ // Ignore
+ }
+
+ @Override
+ public void setHomeButtonEnabled(boolean enable) {
+ // Ignore
+ }
+
+ @Override
+ public int getNavigationMode() {
+ return 0;
+ }
+
+ @Override
+ public void setNavigationMode(int mode) {
+ if (mode != ActionBar.NAVIGATION_MODE_STANDARD) {
+ throw new IllegalArgumentException(
+ "Navigation modes not supported in this configuration");
+ }
+ }
+
+ @Override
+ public void setDropdownParams(SpinnerAdapter adapter,
+ AdapterView.OnItemSelectedListener listener) {
+ if (mSpinner == null) {
+ mSpinner = new Spinner(getContext());
+ }
+ mSpinner.setAdapter(adapter);
+ mSpinner.setOnItemSelectedListener(listener);
+ }
+
+ @Override
+ public void setDropdownSelectedPosition(int position) {
+ if (mSpinner == null) {
+ throw new IllegalStateException(
+ "Can't set dropdown selected position without an adapter");
+ }
+ mSpinner.setSelection(position);
+ }
+
+ @Override
+ public int getDropdownSelectedPosition() {
+ return mSpinner != null ? mSpinner.getSelectedItemPosition() : 0;
+ }
+
+ @Override
+ public int getDropdownItemCount() {
+ return mSpinner != null ? mSpinner.getCount() : 0;
+ }
+
+ @Override
+ public void setCustomView(View view) {
+ if (mCustomView != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
+ mToolbar.removeView(mCustomView);
+ }
+ mCustomView = view;
+ if (view != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
+ mToolbar.addView(mCustomView);
+ }
+ }
+
+ @Override
+ public View getCustomView() {
+ return mCustomView;
+ }
+
+ @Override
+ public void animateToVisibility(int visibility) {
+ if (visibility == View.GONE) {
+ mToolbar.animate().translationY(mToolbar.getHeight()).alpha(0)
+ .setListener(new AnimatorListenerAdapter() {
+ private boolean mCanceled = false;
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mCanceled) {
+ mToolbar.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCanceled = true;
+ }
+ });
+ } else if (visibility == View.VISIBLE) {
+ mToolbar.animate().translationY(0).alpha(1)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mToolbar.setVisibility(View.VISIBLE);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void setNavigationIcon(Drawable icon) {
+ mNavIcon = icon;
+ if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+ mToolbar.setNavigationIcon(icon);
+ }
+ }
+
+ @Override
+ public void setNavigationIcon(int resId) {
+ setNavigationIcon(mToolbar.getContext().getDrawable(resId));
+ }
+
+ @Override
+ public void setNavigationContentDescription(CharSequence description) {
+ mToolbar.setNavigationContentDescription(description);
+ }
+
+ @Override
+ public void setNavigationContentDescription(int resId) {
+ mToolbar.setNavigationContentDescription(resId);
+ }
+
+ @Override
+ public void saveHierarchyState(SparseArray<Parcelable> toolbarStates) {
+ mToolbar.saveHierarchyState(toolbarStates);
+ }
+
+ @Override
+ public void restoreHierarchyState(SparseArray<Parcelable> toolbarStates) {
+ mToolbar.restoreHierarchyState(toolbarStates);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
index 772dc5f..841a02a 100644
--- a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
+++ b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
@@ -238,6 +238,10 @@ public class GlowPadView extends View {
Drawable pointDrawable = pointId != 0 ? context.getDrawable(pointId) : null;
mGlowRadius = a.getDimension(R.styleable.GlowPadView_glowRadius, 0.0f);
+ mPointCloud = new PointCloud(pointDrawable);
+ mPointCloud.makePointCloud(mInnerRadius, mOuterRadius);
+ mPointCloud.glowManager.setRadius(mGlowRadius);
+
TypedValue outValue = new TypedValue();
// Read array of target drawables
@@ -273,10 +277,6 @@ public class GlowPadView extends View {
setVibrateEnabled(mVibrationDuration > 0);
assignDefaultsIfNeeded();
-
- mPointCloud = new PointCloud(pointDrawable);
- mPointCloud.makePointCloud(mInnerRadius, mOuterRadius);
- mPointCloud.glowManager.setRadius(mGlowRadius);
}
private int getResourceId(TypedArray a, int id) {