summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java57
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl2
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl4
-rw-r--r--core/java/android/accounts/AccountManager.java161
-rw-r--r--core/java/android/accounts/IAccountManager.aidl9
-rw-r--r--core/java/android/animation/Animator.java115
-rw-r--r--core/java/android/animation/AnimatorInflater.java90
-rw-r--r--core/java/android/animation/AnimatorSet.java78
-rw-r--r--core/java/android/animation/FloatKeyframeSet.java5
-rw-r--r--core/java/android/animation/IntKeyframeSet.java3
-rw-r--r--core/java/android/animation/KeyframeSet.java18
-rw-r--r--core/java/android/animation/Keyframes.java3
-rw-r--r--core/java/android/animation/ObjectAnimator.java6
-rw-r--r--core/java/android/animation/PropertyValuesHolder.java219
-rw-r--r--core/java/android/animation/StateListAnimator.java148
-rw-r--r--core/java/android/animation/TimeAnimator.java25
-rw-r--r--core/java/android/animation/ValueAnimator.java123
-rw-r--r--core/java/android/app/Activity.java71
-rw-r--r--core/java/android/app/ActivityManager.java21
-rw-r--r--core/java/android/app/ActivityManagerInternal.java4
-rw-r--r--core/java/android/app/ActivityManagerNative.java114
-rw-r--r--core/java/android/app/ActivityOptions.java64
-rw-r--r--core/java/android/app/ActivityThread.java102
-rw-r--r--core/java/android/app/ActivityTransitionCoordinator.java132
-rw-r--r--core/java/android/app/ActivityTransitionState.java24
-rw-r--r--core/java/android/app/AlertDialog.java8
-rw-r--r--core/java/android/app/AppOpsManager.java15
-rw-r--r--core/java/android/app/ApplicationPackageManager.java25
-rw-r--r--core/java/android/app/ApplicationThreadNative.java22
-rw-r--r--core/java/android/app/BackStackRecord.java92
-rw-r--r--core/java/android/app/ContextImpl.java39
-rw-r--r--core/java/android/app/Dialog.java14
-rw-r--r--core/java/android/app/EnterTransitionCoordinator.java59
-rw-r--r--core/java/android/app/IActivityManager.java17
-rw-r--r--core/java/android/app/IApplicationThread.java9
-rw-r--r--core/java/android/app/INotificationManager.aidl1
-rw-r--r--core/java/android/app/ITaskStackListener.aidl22
-rw-r--r--core/java/android/app/Instrumentation.java18
-rw-r--r--core/java/android/app/KeyguardManager.java35
-rw-r--r--core/java/android/app/LoadedApk.java91
-rw-r--r--core/java/android/app/LocalActivityManager.java5
-rw-r--r--core/java/android/app/Notification.java202
-rw-r--r--core/java/android/app/NotificationManager.java12
-rw-r--r--core/java/android/app/SharedElementCallback.java48
-rw-r--r--core/java/android/app/TimePickerDialog.java3
-rw-r--r--core/java/android/app/UiAutomation.java3
-rw-r--r--core/java/android/app/WallpaperManager.java41
-rw-r--r--core/java/android/app/admin/DeviceAdminReceiver.java13
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java262
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl10
-rw-r--r--core/java/android/app/backup/BackupHelperDispatcher.java1
-rw-r--r--core/java/android/app/backup/BackupTransport.java7
-rw-r--r--core/java/android/app/backup/IBackupManager.aidl19
-rw-r--r--core/java/android/app/backup/RecentsBackupHelper.java114
-rw-r--r--core/java/android/app/backup/WallpaperBackupHelper.java9
-rw-r--r--core/java/android/app/trust/ITrustManager.aidl2
-rw-r--r--core/java/android/app/trust/TrustManager.java13
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java22
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java62
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java34
-rw-r--r--core/java/android/bluetooth/BluetoothGatt.java4
-rw-r--r--core/java/android/bluetooth/BluetoothGattCharacteristic.java2
-rw-r--r--core/java/android/bluetooth/BluetoothGattServer.java18
-rw-r--r--core/java/android/bluetooth/BluetoothGattServerCallback.java12
-rw-r--r--core/java/android/bluetooth/BluetoothHeadset.java92
-rw-r--r--core/java/android/bluetooth/BluetoothUuid.java2
-rw-r--r--core/java/android/bluetooth/IBluetooth.aidl6
-rw-r--r--core/java/android/bluetooth/IBluetoothGattServerCallback.aidl1
-rw-r--r--core/java/android/bluetooth/IBluetoothManager.aidl4
-rwxr-xr-xcore/java/android/bluetooth/IBluetoothProfileServiceConnection.aidl30
-rw-r--r--core/java/android/bluetooth/le/BluetoothLeAdvertiser.java15
-rw-r--r--core/java/android/bluetooth/le/BluetoothLeScanner.java5
-rw-r--r--core/java/android/content/ContentProvider.java94
-rw-r--r--core/java/android/content/ContentProviderClient.java2
-rw-r--r--core/java/android/content/ContentProviderNative.java6
-rw-r--r--core/java/android/content/Context.java28
-rw-r--r--core/java/android/content/ContextWrapper.java13
-rw-r--r--core/java/android/content/IContentProvider.java3
-rw-r--r--core/java/android/content/Intent.java364
-rw-r--r--core/java/android/content/UriMatcher.java5
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java7
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl1
-rw-r--r--core/java/android/content/pm/LabeledIntent.aidl19
-rw-r--r--core/java/android/content/pm/LauncherApps.java16
-rw-r--r--core/java/android/content/pm/PackageInfo.java31
-rw-r--r--core/java/android/content/pm/PackageInfoLite.java16
-rw-r--r--core/java/android/content/pm/PackageItemInfo.java19
-rw-r--r--core/java/android/content/pm/PackageManager.java12
-rw-r--r--core/java/android/content/pm/PackageParser.java66
-rw-r--r--core/java/android/content/pm/PackageUserState.java12
-rw-r--r--core/java/android/content/pm/ParceledListSlice.java47
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java52
-rw-r--r--core/java/android/content/pm/Signature.java51
-rw-r--r--core/java/android/content/res/AssetManager.java8
-rw-r--r--core/java/android/content/res/ColorStateList.java2
-rw-r--r--core/java/android/content/res/Configuration.java6
-rw-r--r--core/java/android/content/res/ConfigurationBoundResourceCache.java138
-rw-r--r--core/java/android/content/res/ConstantState.java61
-rw-r--r--core/java/android/content/res/Resources.java101
-rw-r--r--core/java/android/content/res/ResourcesKey.java13
-rw-r--r--core/java/android/content/res/TypedArray.java24
-rw-r--r--core/java/android/hardware/Sensor.java90
-rw-r--r--core/java/android/hardware/SensorEventListener2.java13
-rw-r--r--core/java/android/hardware/SensorManager.java254
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java28
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java46
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java335
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java111
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java36
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java36
-rw-r--r--core/java/android/hardware/camera2/DngCreator.java6
-rw-r--r--core/java/android/hardware/camera2/legacy/GLThreadManager.java14
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java97
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java12
-rw-r--r--core/java/android/hardware/camera2/legacy/ParameterUtils.java4
-rw-r--r--core/java/android/hardware/camera2/legacy/RequestThreadManager.java119
-rw-r--r--core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java176
-rw-r--r--core/java/android/hardware/camera2/params/StreamConfigurationMap.java52
-rw-r--r--core/java/android/hardware/camera2/utils/ArrayUtils.java2
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java8
-rw-r--r--core/java/android/hardware/hdmi/HdmiClient.java37
-rw-r--r--core/java/android/hardware/hdmi/HdmiControlManager.java68
-rw-r--r--core/java/android/hardware/hdmi/HdmiDeviceInfo.java95
-rw-r--r--core/java/android/hardware/hdmi/HdmiHotplugEvent.java12
-rw-r--r--core/java/android/hardware/hdmi/HdmiPlaybackClient.java6
-rw-r--r--core/java/android/hardware/hdmi/HdmiPortInfo.java17
-rw-r--r--core/java/android/hardware/hdmi/HdmiRecordListener.java16
-rw-r--r--core/java/android/hardware/hdmi/HdmiRecordSources.java42
-rw-r--r--core/java/android/hardware/hdmi/HdmiTimerRecordSources.java33
-rw-r--r--core/java/android/hardware/hdmi/HdmiTvClient.java62
-rw-r--r--core/java/android/hardware/hdmi/IHdmiControlService.aidl1
-rw-r--r--core/java/android/hardware/hdmi/IHdmiRecordListener.aidl16
-rw-r--r--core/java/android/hardware/hdmi/IHdmiVendorCommandListener.aidl5
-rw-r--r--core/java/android/hardware/location/GeofenceHardwareImpl.java64
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTrigger.java59
-rw-r--r--core/java/android/inputmethodservice/KeyboardView.java6
-rw-r--r--core/java/android/net/BaseNetworkStateTracker.java215
-rw-r--r--core/java/android/net/ConnectivityManager.java338
-rw-r--r--core/java/android/net/EthernetManager.java97
-rw-r--r--core/java/android/net/IConnectivityManager.aidl20
-rw-r--r--core/java/android/net/IEthernetManager.aidl4
-rw-r--r--core/java/android/net/IEthernetServiceListener.aidl23
-rw-r--r--core/java/android/net/INetworkStatsService.aidl4
-rw-r--r--core/java/android/net/LinkAddress.java26
-rw-r--r--core/java/android/net/LinkProperties.java43
-rw-r--r--core/java/android/net/MobileDataStateTracker.java919
-rw-r--r--core/java/android/net/Network.java67
-rw-r--r--core/java/android/net/NetworkCapabilities.java34
-rw-r--r--core/java/android/net/NetworkFactory.java13
-rw-r--r--core/java/android/net/NetworkIdentity.java58
-rw-r--r--core/java/android/net/NetworkMisc.java17
-rw-r--r--core/java/android/net/NetworkPolicy.java2
-rw-r--r--core/java/android/net/NetworkScoreManager.java19
-rw-r--r--core/java/android/net/NetworkScorerAppManager.java6
-rw-r--r--core/java/android/net/NetworkState.java14
-rw-r--r--core/java/android/net/NetworkStateTracker.java260
-rw-r--r--core/java/android/net/NetworkStats.java16
-rw-r--r--core/java/android/net/NetworkStatsHistory.java48
-rw-r--r--core/java/android/net/NetworkTemplate.java99
-rw-r--r--core/java/android/net/Proxy.java1
-rw-r--r--core/java/android/net/ProxyDataTracker.java207
-rw-r--r--core/java/android/net/ProxyInfo.java9
-rw-r--r--core/java/android/net/PskKeyManager.java7
-rw-r--r--core/java/android/net/RssiCurve.java55
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java6
-rw-r--r--core/java/android/net/SamplingDataTracker.java300
-rw-r--r--core/java/android/net/VpnService.java84
-rw-r--r--core/java/android/net/http/AndroidHttpClient.java18
-rw-r--r--core/java/android/nfc/BeamShareData.java9
-rw-r--r--core/java/android/nfc/INfcAdapter.aidl2
-rw-r--r--core/java/android/nfc/NfcActivityManager.java41
-rw-r--r--core/java/android/os/AsyncTask.java24
-rw-r--r--core/java/android/os/BaseBundle.java10
-rw-r--r--core/java/android/os/BatteryStats.java162
-rw-r--r--core/java/android/os/Build.java34
-rw-r--r--core/java/android/os/Bundle.java62
-rw-r--r--core/java/android/os/ConditionVariable.java2
-rw-r--r--core/java/android/os/Debug.java58
-rw-r--r--core/java/android/os/FileUtils.java80
-rw-r--r--core/java/android/os/Handler.java6
-rw-r--r--core/java/android/os/INetworkManagementService.aidl15
-rw-r--r--core/java/android/os/IPowerManager.aidl2
-rw-r--r--core/java/android/os/Message.java40
-rw-r--r--core/java/android/os/Parcel.java12
-rw-r--r--core/java/android/os/PersistableBundle.java3
-rw-r--r--core/java/android/os/PowerManager.java37
-rw-r--r--core/java/android/os/PowerManagerInternal.java59
-rw-r--r--core/java/android/os/UserHandle.java4
-rw-r--r--core/java/android/os/UserManager.java44
-rw-r--r--core/java/android/os/storage/IMountService.java63
-rw-r--r--core/java/android/preference/ListPreference.java4
-rw-r--r--core/java/android/preference/Preference.java2
-rw-r--r--core/java/android/preference/PreferenceManager.java2
-rw-r--r--core/java/android/preference/SeekBarVolumizer.java138
-rw-r--r--core/java/android/preference/VolumePreference.java13
-rw-r--r--core/java/android/print/IPrintDocumentAdapter.aidl1
-rw-r--r--core/java/android/print/PrintManager.java21
-rw-r--r--core/java/android/provider/CallLog.java7
-rw-r--r--core/java/android/provider/DocumentsProvider.java6
-rw-r--r--core/java/android/provider/Settings.java128
-rw-r--r--core/java/android/service/carrier/CarrierMessagingService.java417
-rw-r--r--core/java/android/service/carrier/ICarrierMessagingCallback.aidl30
-rw-r--r--core/java/android/service/carrier/ICarrierMessagingService.aidl110
-rw-r--r--core/java/android/service/carrier/MessagePdu.aidl19
-rw-r--r--core/java/android/service/carrier/MessagePdu.java97
-rw-r--r--core/java/android/service/notification/Condition.java2
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java74
-rw-r--r--core/java/android/service/trust/ITrustAgentService.aidl6
-rw-r--r--core/java/android/service/trust/ITrustAgentServiceCallback.aidl2
-rw-r--r--core/java/android/service/trust/TrustAgentService.java101
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java73
-rw-r--r--core/java/android/text/StaticLayout.java14
-rw-r--r--core/java/android/text/TextLine.java3
-rwxr-xr-xcore/java/android/text/format/DateFormat.java100
-rw-r--r--core/java/android/text/format/Formatter.java21
-rw-r--r--core/java/android/text/format/Time.java3
-rw-r--r--core/java/android/transition/ChangeBounds.java418
-rw-r--r--core/java/android/transition/ChangeTransform.java110
-rw-r--r--core/java/android/transition/SidePropagation.java22
-rw-r--r--core/java/android/transition/Slide.java40
-rw-r--r--core/java/android/transition/Transition.java10
-rw-r--r--core/java/android/transition/TransitionUtils.java31
-rw-r--r--core/java/android/transition/Visibility.java12
-rw-r--r--core/java/android/util/ArrayMap.java5
-rw-r--r--core/java/android/util/ArraySet.java9
-rw-r--r--core/java/android/util/DisplayMetrics.java7
-rw-r--r--core/java/android/util/FloatMath.java3
-rw-r--r--core/java/android/util/IntArray.java162
-rw-r--r--core/java/android/util/Log.java5
-rw-r--r--core/java/android/util/NtpTrustedTime.java20
-rw-r--r--core/java/android/util/PathParser.java75
-rw-r--r--core/java/android/util/Slog.java7
-rw-r--r--core/java/android/util/TypedValue.java23
-rw-r--r--core/java/android/view/AccessibilityInteractionController.java107
-rw-r--r--core/java/android/view/DisplayInfo.java12
-rw-r--r--core/java/android/view/GLES20Canvas.java34
-rw-r--r--core/java/android/view/HardwareCanvas.java5
-rw-r--r--core/java/android/view/HardwareLayer.java5
-rw-r--r--core/java/android/view/HardwareRenderer.java2
-rw-r--r--core/java/android/view/IWindow.aidl5
-rw-r--r--core/java/android/view/IWindowManager.aidl1
-rw-r--r--core/java/android/view/IWindowSession.aidl9
-rw-r--r--core/java/android/view/InputEvent.java7
-rw-r--r--core/java/android/view/KeyEvent.java10
-rw-r--r--core/java/android/view/MotionEvent.java37
-rw-r--r--core/java/android/view/RenderNodeAnimator.java68
-rw-r--r--core/java/android/view/Surface.java123
-rw-r--r--core/java/android/view/SurfaceView.java3
-rw-r--r--core/java/android/view/ThreadedRenderer.java32
-rw-r--r--core/java/android/view/View.java827
-rw-r--r--core/java/android/view/ViewAnimationUtils.java7
-rw-r--r--core/java/android/view/ViewDebug.java35
-rw-r--r--core/java/android/view/ViewGroup.java421
-rw-r--r--core/java/android/view/ViewOverlay.java3
-rw-r--r--core/java/android/view/ViewParent.java20
-rw-r--r--core/java/android/view/ViewRootImpl.java205
-rw-r--r--core/java/android/view/ViewTreeObserver.java135
-rw-r--r--core/java/android/view/Window.java128
-rw-r--r--core/java/android/view/WindowManager.java142
-rw-r--r--core/java/android/view/WindowManagerGlobal.java12
-rw-r--r--core/java/android/view/WindowManagerImpl.java36
-rw-r--r--core/java/android/view/WindowManagerInternal.java16
-rw-r--r--core/java/android/view/WindowManagerPolicy.java19
-rw-r--r--core/java/android/view/accessibility/AccessibilityCache.java33
-rw-r--r--core/java/android/view/accessibility/AccessibilityInteractionClient.java68
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java32
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java168
-rw-r--r--core/java/android/view/accessibility/AccessibilityWindowInfo.java35
-rw-r--r--core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl4
-rw-r--r--core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl8
-rw-r--r--core/java/android/view/animation/AccelerateDecelerateInterpolator.java8
-rw-r--r--core/java/android/view/animation/AccelerateInterpolator.java4
-rw-r--r--core/java/android/view/animation/AnimationUtils.java5
-rw-r--r--core/java/android/view/animation/AnticipateInterpolator.java7
-rw-r--r--core/java/android/view/animation/AnticipateOvershootInterpolator.java5
-rw-r--r--core/java/android/view/animation/BaseInterpolator.java37
-rw-r--r--core/java/android/view/animation/BounceInterpolator.java2
-rw-r--r--core/java/android/view/animation/CycleInterpolator.java4
-rw-r--r--core/java/android/view/animation/DecelerateInterpolator.java4
-rw-r--r--core/java/android/view/animation/LinearInterpolator.java7
-rw-r--r--core/java/android/view/animation/OvershootInterpolator.java7
-rw-r--r--core/java/android/view/animation/PathInterpolator.java4
-rw-r--r--core/java/android/webkit/WebView.java11
-rw-r--r--core/java/android/webkit/WebViewDelegate.java2
-rw-r--r--core/java/android/webkit/WebViewFactory.java12
-rw-r--r--core/java/android/widget/AbsListView.java85
-rw-r--r--core/java/android/widget/AbsSeekBar.java75
-rw-r--r--core/java/android/widget/ActionMenuPresenter.java16
-rw-r--r--core/java/android/widget/ActionMenuView.java2
-rw-r--r--core/java/android/widget/AdapterView.java47
-rw-r--r--core/java/android/widget/AppSecurityPermissions.java4
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java6
-rw-r--r--core/java/android/widget/CalendarView.java1607
-rw-r--r--core/java/android/widget/CalendarViewLegacyDelegate.java1527
-rw-r--r--core/java/android/widget/CalendarViewMaterialDelegate.java260
-rw-r--r--core/java/android/widget/CheckedTextView.java6
-rw-r--r--core/java/android/widget/CompoundButton.java24
-rw-r--r--core/java/android/widget/DatePicker.java28
-rwxr-xr-xcore/java/android/widget/DatePickerCalendarDelegate.java125
-rw-r--r--core/java/android/widget/DatePickerController.java22
-rw-r--r--core/java/android/widget/DateTimeView.java116
-rw-r--r--core/java/android/widget/DayPickerView.java195
-rw-r--r--core/java/android/widget/FastScroller.java57
-rw-r--r--core/java/android/widget/FrameLayout.java6
-rw-r--r--core/java/android/widget/GridLayout.java81
-rw-r--r--core/java/android/widget/HorizontalScrollView.java3
-rw-r--r--core/java/android/widget/ImageView.java32
-rw-r--r--core/java/android/widget/ListPopupWindow.java52
-rw-r--r--core/java/android/widget/OverScroller.java4
-rw-r--r--core/java/android/widget/PopupMenu.java57
-rw-r--r--core/java/android/widget/PopupWindow.java143
-rw-r--r--core/java/android/widget/ProgressBar.java184
-rw-r--r--core/java/android/widget/RadialTimePickerView.java895
-rw-r--r--core/java/android/widget/RatingBar.java4
-rw-r--r--core/java/android/widget/RemoteViews.java70
-rw-r--r--core/java/android/widget/ScrollBarDrawable.java43
-rw-r--r--core/java/android/widget/SearchView.java213
-rw-r--r--core/java/android/widget/SimpleMonthAdapter.java151
-rw-r--r--core/java/android/widget/SimpleMonthView.java23
-rw-r--r--core/java/android/widget/Switch.java14
-rw-r--r--core/java/android/widget/TextClock.java34
-rw-r--r--core/java/android/widget/TextView.java52
-rw-r--r--core/java/android/widget/TimePicker.java4
-rw-r--r--core/java/android/widget/TimePickerClockDelegate.java1451
-rw-r--r--core/java/android/widget/TimePickerSpinnerDelegate.java1405
-rw-r--r--core/java/android/widget/Toast.java8
-rw-r--r--core/java/android/widget/Toolbar.java24
-rw-r--r--core/java/android/widget/VideoView.java11
-rw-r--r--core/java/android/widget/YearPickerView.java32
329 files changed, 15330 insertions, 9285 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 13ceb4a..3f1845a 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -16,6 +16,7 @@
package android.accessibilityservice;
+import android.annotation.NonNull;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
@@ -25,12 +26,16 @@ import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.view.KeyEvent;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
import java.util.List;
@@ -366,7 +371,7 @@ public abstract class AccessibilityService extends Service {
public void onAccessibilityEvent(AccessibilityEvent event);
public void onInterrupt();
public void onServiceConnected();
- public void onSetConnectionId(int connectionId);
+ public void init(int connectionId, IBinder windowToken);
public boolean onGesture(int gestureId);
public boolean onKeyEvent(KeyEvent event);
}
@@ -375,6 +380,10 @@ public abstract class AccessibilityService extends Service {
private AccessibilityServiceInfo mInfo;
+ private IBinder mWindowToken;
+
+ private WindowManager mWindowManager;
+
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
*
@@ -611,6 +620,23 @@ public abstract class AccessibilityService extends Service {
}
}
+ @Override
+ public Object getSystemService(@ServiceName @NonNull String name) {
+ if (getBaseContext() == null) {
+ throw new IllegalStateException(
+ "System services not available to Activities before onCreate()");
+ }
+
+ // Guarantee that we always return the same window manager instance.
+ if (WINDOW_SERVICE.equals(name)) {
+ if (mWindowManager == null) {
+ mWindowManager = (WindowManager) getBaseContext().getSystemService(name);
+ }
+ return mWindowManager;
+ }
+ return super.getSystemService(name);
+ }
+
/**
* Implement to return the implementation of the internal accessibility
* service interface.
@@ -634,8 +660,14 @@ public abstract class AccessibilityService extends Service {
}
@Override
- public void onSetConnectionId( int connectionId) {
+ public void init(int connectionId, IBinder windowToken) {
mConnectionId = connectionId;
+ mWindowToken = windowToken;
+
+ // The client may have already obtained the window manager, so
+ // update the default token on whatever manager we gave them.
+ final WindowManagerImpl wm = (WindowManagerImpl) getSystemService(WINDOW_SERVICE);
+ wm.setDefaultToken(windowToken);
}
@Override
@@ -658,7 +690,7 @@ public abstract class AccessibilityService extends Service {
*/
public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
implements HandlerCaller.Callback {
- private static final int DO_SET_SET_CONNECTION = 1;
+ private static final int DO_INIT = 1;
private static final int DO_ON_INTERRUPT = 2;
private static final int DO_ON_ACCESSIBILITY_EVENT = 3;
private static final int DO_ON_GESTURE = 4;
@@ -677,9 +709,10 @@ public abstract class AccessibilityService extends Service {
mCaller = new HandlerCaller(context, looper, this, true /*asyncHandler*/);
}
- public void setConnection(IAccessibilityServiceConnection connection, int connectionId) {
- Message message = mCaller.obtainMessageIO(DO_SET_SET_CONNECTION, connectionId,
- connection);
+ public void init(IAccessibilityServiceConnection connection, int connectionId,
+ IBinder windowToken) {
+ Message message = mCaller.obtainMessageIOO(DO_INIT, connectionId,
+ connection, windowToken);
mCaller.sendMessage(message);
}
@@ -730,20 +763,24 @@ public abstract class AccessibilityService extends Service {
mCallback.onInterrupt();
} return;
- case DO_SET_SET_CONNECTION: {
+ case DO_INIT: {
mConnectionId = message.arg1;
+ SomeArgs args = (SomeArgs) message.obj;
IAccessibilityServiceConnection connection =
- (IAccessibilityServiceConnection) message.obj;
+ (IAccessibilityServiceConnection) args.arg1;
+ IBinder windowToken = (IBinder) args.arg2;
+ args.recycle();
if (connection != null) {
AccessibilityInteractionClient.getInstance().addConnection(mConnectionId,
connection);
- mCallback.onSetConnectionId(mConnectionId);
+ mCallback.init(mConnectionId, windowToken);
mCallback.onServiceConnected();
} else {
AccessibilityInteractionClient.getInstance().removeConnection(
mConnectionId);
+ mConnectionId = AccessibilityInteractionClient.NO_ID;
AccessibilityInteractionClient.getInstance().clearCache();
- mCallback.onSetConnectionId(AccessibilityInteractionClient.NO_ID);
+ mCallback.init(AccessibilityInteractionClient.NO_ID, null);
}
} return;
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 6ce0219..8b503dd 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -28,7 +28,7 @@ import android.view.KeyEvent;
*/
oneway interface IAccessibilityServiceClient {
- void setConnection(in IAccessibilityServiceConnection connection, int connectionId);
+ void init(in IAccessibilityServiceConnection connection, int connectionId, IBinder windowToken);
void onAccessibilityEvent(in AccessibilityEvent event);
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 27a03b6..5f7a17d 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -54,10 +54,6 @@ interface IAccessibilityServiceConnection {
int action, in Bundle arguments, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long threadId);
- boolean computeClickPointInScreen(int accessibilityWindowId, long accessibilityNodeId,
- int interactionId, IAccessibilityInteractionConnectionCallback callback,
- long threadId);
-
AccessibilityWindowInfo getWindow(int windowId);
List<AccessibilityWindowInfo> getWindows();
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 09b484b..6957435 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -17,37 +17,37 @@
package android.accounts;
import android.app.Activity;
-import android.content.Intent;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.IntentFilter;
-import android.content.BroadcastReceiver;
import android.content.res.Resources;
import android.database.SQLException;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.os.RemoteException;
import android.os.Parcelable;
-import android.os.Build;
import android.os.Process;
+import android.os.RemoteException;
import android.os.UserHandle;
-import android.util.Log;
import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.google.android.collect.Maps;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
-import java.util.concurrent.TimeoutException;
import java.util.concurrent.TimeUnit;
-import java.util.HashMap;
-import java.util.Map;
-
-import com.android.internal.R;
-import com.google.android.collect.Maps;
+import java.util.concurrent.TimeoutException;
/**
* This class provides access to a centralized registry of the user's
@@ -747,13 +747,17 @@ public class AccountManager {
* null for the main thread
* @return An {@link AccountManagerFuture} which resolves to a Boolean,
* true if the account has been successfully removed
+ * @deprecated use
+ * {@link #removeAccount(Account, Activity, AccountManagerCallback, Handler)}
+ * instead
*/
+ @Deprecated
public AccountManagerFuture<Boolean> removeAccount(final Account account,
AccountManagerCallback<Boolean> callback, Handler handler) {
if (account == null) throw new IllegalArgumentException("account is null");
return new Future2Task<Boolean>(handler, callback) {
public void doWork() throws RemoteException {
- mService.removeAccount(mResponse, account);
+ mService.removeAccount(mResponse, account, false);
}
public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
@@ -765,9 +769,60 @@ public class AccountManager {
}
/**
+ * Removes an account from the AccountManager. Does nothing if the account
+ * does not exist. Does not delete the account from the server.
+ * The authenticator may have its own policies preventing account
+ * deletion, in which case the account will not be deleted.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ *
+ * @param account The {@link Account} to remove
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to delete an
+ * account; used only to call startActivity(); if null, the prompt
+ * will not be launched directly, but the {@link Intent} may be
+ * returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * {@link #KEY_BOOLEAN_RESULT} if activity was specified and an account
+ * was removed or if active. If no activity was specified, the returned
+ * Bundle contains only {@link #KEY_INTENT} with the {@link Intent}
+ * needed to launch the actual account removal process, if authenticator
+ * needs the activity launch. If an error occurred,
+ * {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if no authenticator was registered for
+ * this account type or the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the creation process or
+ * adding accounts (of this type) has been disabled by policy
+ * </ul>
+ */
+ public AccountManagerFuture<Bundle> removeAccount(final Account account,
+ final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ return new AmsTask(activity, handler, callback) {
+ public void doWork() throws RemoteException {
+ mService.removeAccount(mResponse, account, activity != null);
+ }
+ }.start();
+ }
+
+ /**
* @see #removeAccount(Account, AccountManagerCallback, Handler)
* @hide
+ * @deprecated use
+ * {@link #removeAccountAsUser(Account, Activity, AccountManagerCallback, Handler)}
+ * instead
*/
+ @Deprecated
public AccountManagerFuture<Boolean> removeAccountAsUser(final Account account,
AccountManagerCallback<Boolean> callback, Handler handler,
final UserHandle userHandle) {
@@ -775,7 +830,7 @@ public class AccountManager {
if (userHandle == null) throw new IllegalArgumentException("userHandle is null");
return new Future2Task<Boolean>(handler, callback) {
public void doWork() throws RemoteException {
- mService.removeAccountAsUser(mResponse, account, userHandle.getIdentifier());
+ mService.removeAccountAsUser(mResponse, account, false, userHandle.getIdentifier());
}
public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
@@ -787,6 +842,52 @@ public class AccountManager {
}
/**
+ * @see #removeAccount(Account, Activity, AccountManagerCallback, Handler)
+ * @hide
+ */
+ public AccountManagerFuture<Bundle> removeAccountAsUser(final Account account,
+ final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler,
+ final UserHandle userHandle) {
+ if (account == null)
+ throw new IllegalArgumentException("account is null");
+ if (userHandle == null)
+ throw new IllegalArgumentException("userHandle is null");
+ return new AmsTask(activity, handler, callback) {
+ public void doWork() throws RemoteException {
+ mService.removeAccountAsUser(mResponse, account, activity != null,
+ userHandle.getIdentifier());
+ }
+ }.start();
+ }
+
+ /**
+ * Removes an account directly. Normally used by authenticators, not
+ * directly by applications. Does not delete the account from the server.
+ * The authenticator may have its own policies preventing account deletion,
+ * in which case the account will not be deleted.
+ * <p>
+ * It is safe to call this method from the main thread.
+ * <p>
+ * This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and to have the
+ * same UID or signature as the account's authenticator.
+ *
+ * @param account The {@link Account} to delete.
+ * @return True if the account was successfully deleted, false if the
+ * account did not exist, the account is null, or another error
+ * occurs.
+ */
+ public boolean removeAccountExplicitly(Account account) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ try {
+ return mService.removeAccountExplicitly(account);
+ } catch (RemoteException e) {
+ // won't ever happen
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
* Removes an auth token from the AccountManager's cache. Does nothing if
* the auth token is not currently in the cache. Applications must call this
* method when the auth token is found to have expired or otherwise become
@@ -1342,6 +1443,40 @@ public class AccountManager {
}
/**
+ * Copies an account from the primary user to another user.
+ * @param account the account to copy
+ * @param user the target user
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated wether it
+ * succeeded.
+ * @hide
+ */
+ public AccountManagerFuture<Boolean> copyAccountToUser(
+ final Account account, final UserHandle user,
+ AccountManagerCallback<Boolean> callback, Handler handler) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (user == null) throw new IllegalArgumentException("user is null");
+
+ return new Future2Task<Boolean>(handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.copyAccountToUser(
+ mResponse, account, UserHandle.USER_OWNER, user.getIdentifier());
+ }
+ @Override
+ public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ return bundle.getBoolean(KEY_BOOLEAN_RESULT);
+ }
+ }.start();
+ }
+
+ /**
* @hide
* Removes the shared account.
* @param account the account to remove
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index a133788..aa41161 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -37,8 +37,13 @@ interface IAccountManager {
void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features);
void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, in String[] features);
boolean addAccountExplicitly(in Account account, String password, in Bundle extras);
- void removeAccount(in IAccountManagerResponse response, in Account account);
- void removeAccountAsUser(in IAccountManagerResponse response, in Account account, int userId);
+ void removeAccount(in IAccountManagerResponse response, in Account account,
+ boolean expectActivityLaunch);
+ void removeAccountAsUser(in IAccountManagerResponse response, in Account account,
+ boolean expectActivityLaunch, int userId);
+ boolean removeAccountExplicitly(in Account account);
+ void copyAccountToUser(in IAccountManagerResponse response, in Account account,
+ int userFrom, int userTo);
void invalidateAuthToken(String accountType, String authToken);
String peekAuthToken(in Account account, String authTokenType);
void setAuthToken(in Account account, String authTokenType, String authToken);
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 3720c81..da48709 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -16,6 +16,8 @@
package android.animation;
+import android.content.res.ConstantState;
+
import java.util.ArrayList;
/**
@@ -41,6 +43,18 @@ public abstract class Animator implements Cloneable {
boolean mPaused = false;
/**
+ * A set of flags which identify the type of configuration changes that can affect this
+ * Animator. Used by the Animator cache.
+ */
+ int mChangingConfigurations = 0;
+
+ /**
+ * If this animator is inflated from a constant state, keep a reference to it so that
+ * ConstantState will not be garbage collected until this animator is collected
+ */
+ private AnimatorConstantState mConstantState;
+
+ /**
* Starts this animation. If the animation has a nonzero startDelay, the animation will start
* running after that delay elapses. A non-delayed animation will have its initial
* value(s) set immediately, followed by calls to
@@ -295,25 +309,71 @@ public abstract class Animator implements Cloneable {
}
}
+ /**
+ * Return a mask of the configuration parameters for which this animator may change, requiring
+ * that it should be re-created from Resources. The default implementation returns whatever
+ * value was provided through setChangingConfigurations(int) or 0 by default.
+ *
+ * @return Returns a mask of the changing configuration parameters, as defined by
+ * {@link android.content.pm.ActivityInfo}.
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
+ /**
+ * Set a mask of the configuration parameters for which this animator may change, requiring
+ * that it be re-created from resource.
+ *
+ * @param configs A mask of the changing configuration parameters, as
+ * defined by {@link android.content.pm.ActivityInfo}.
+ *
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public void setChangingConfigurations(int configs) {
+ mChangingConfigurations = configs;
+ }
+
+ /**
+ * Sets the changing configurations value to the union of the current changing configurations
+ * and the provided configs.
+ * This method is called while loading the animator.
+ * @hide
+ */
+ public void appendChangingConfigurations(int configs) {
+ mChangingConfigurations |= configs;
+ }
+
+ /**
+ * Return a {@link android.content.res.ConstantState} instance that holds the shared state of
+ * this Animator.
+ * <p>
+ * This constant state is used to create new instances of this animator when needed, instead
+ * of re-loading it from resources. Default implementation creates a new
+ * {@link AnimatorConstantState}. You can override this method to provide your custom logic or
+ * return null if you don't want this animator to be cached.
+ *
+ * @return The ConfigurationBoundResourceCache.BaseConstantState associated to this Animator.
+ * @see android.content.res.ConstantState
+ * @see #clone()
+ * @hide
+ */
+ public ConstantState<Animator> createConstantState() {
+ return new AnimatorConstantState(this);
+ }
+
@Override
public Animator clone() {
try {
final Animator anim = (Animator) super.clone();
if (mListeners != null) {
- ArrayList<AnimatorListener> oldListeners = mListeners;
- anim.mListeners = new ArrayList<AnimatorListener>();
- int numListeners = oldListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- anim.mListeners.add(oldListeners.get(i));
- }
+ anim.mListeners = new ArrayList<AnimatorListener>(mListeners);
}
if (mPauseListeners != null) {
- ArrayList<AnimatorPauseListener> oldListeners = mPauseListeners;
- anim.mPauseListeners = new ArrayList<AnimatorPauseListener>();
- int numListeners = oldListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- anim.mPauseListeners.add(oldListeners.get(i));
- }
+ anim.mPauseListeners = new ArrayList<AnimatorPauseListener>(mPauseListeners);
}
return anim;
} catch (CloneNotSupportedException e) {
@@ -469,4 +529,35 @@ public abstract class Animator implements Cloneable {
public void setAllowRunningAsynchronously(boolean mayRunAsync) {
// It is up to subclasses to support this, if they can.
}
+
+ /**
+ * Creates a {@link ConstantState} which holds changing configurations information associated
+ * with the given Animator.
+ * <p>
+ * When {@link #newInstance()} is called, default implementation clones the Animator.
+ */
+ private static class AnimatorConstantState extends ConstantState<Animator> {
+
+ final Animator mAnimator;
+ int mChangingConf;
+
+ public AnimatorConstantState(Animator animator) {
+ mAnimator = animator;
+ // ensure a reference back to here so that constante state is not gc'ed.
+ mAnimator.mConstantState = this;
+ mChangingConf = mAnimator.getChangingConfigurations();
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mChangingConf;
+ }
+
+ @Override
+ public Animator newInstance() {
+ final Animator clone = mAnimator.clone();
+ clone.mConstantState = this;
+ return clone;
+ }
+ }
}
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 25417ed..688d7e4 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -16,6 +16,8 @@
package android.animation;
import android.content.Context;
+import android.content.res.ConfigurationBoundResourceCache;
+import android.content.res.ConstantState;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
@@ -30,6 +32,8 @@ import android.util.TypedValue;
import android.util.Xml;
import android.view.InflateException;
import android.view.animation.AnimationUtils;
+import android.view.animation.BaseInterpolator;
+import android.view.animation.Interpolator;
import com.android.internal.R;
@@ -67,6 +71,9 @@ public class AnimatorInflater {
private static final boolean DBG_ANIMATOR_INFLATER = false;
+ // used to calculate changing configs for resource references
+ private static final TypedValue sTmpTypedValue = new TypedValue();
+
/**
* Loads an {@link Animator} object from a resource
*
@@ -98,11 +105,34 @@ public class AnimatorInflater {
/** @hide */
public static Animator loadAnimator(Resources resources, Theme theme, int id,
float pathErrorScale) throws NotFoundException {
-
+ final ConfigurationBoundResourceCache<Animator> animatorCache = resources
+ .getAnimatorCache();
+ Animator animator = animatorCache.get(id, theme);
+ if (animator != null) {
+ if (DBG_ANIMATOR_INFLATER) {
+ Log.d(TAG, "loaded animator from cache, " + resources.getResourceName(id));
+ }
+ return animator;
+ } else if (DBG_ANIMATOR_INFLATER) {
+ Log.d(TAG, "cache miss for animator " + resources.getResourceName(id));
+ }
XmlResourceParser parser = null;
try {
parser = resources.getAnimation(id);
- return createAnimatorFromXml(resources, theme, parser, pathErrorScale);
+ animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale);
+ if (animator != null) {
+ animator.appendChangingConfigurations(getChangingConfigs(resources, id));
+ final ConstantState<Animator> constantState = animator.createConstantState();
+ if (constantState != null) {
+ if (DBG_ANIMATOR_INFLATER) {
+ Log.d(TAG, "caching animator for res " + resources.getResourceName(id));
+ }
+ animatorCache.put(id, theme, constantState);
+ // create a new animator so that cached version is never used by the user
+ animator = constantState.newInstance(resources, theme);
+ }
+ }
+ return animator;
} catch (XmlPullParserException ex) {
Resources.NotFoundException rnf =
new Resources.NotFoundException("Can't load animation resource ID #0x" +
@@ -122,10 +152,29 @@ public class AnimatorInflater {
public static StateListAnimator loadStateListAnimator(Context context, int id)
throws NotFoundException {
+ final Resources resources = context.getResources();
+ final ConfigurationBoundResourceCache<StateListAnimator> cache = resources
+ .getStateListAnimatorCache();
+ final Theme theme = context.getTheme();
+ StateListAnimator animator = cache.get(id, theme);
+ if (animator != null) {
+ return animator;
+ }
XmlResourceParser parser = null;
try {
- parser = context.getResources().getAnimation(id);
- return createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser));
+ parser = resources.getAnimation(id);
+ animator = createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser));
+ if (animator != null) {
+ animator.appendChangingConfigurations(getChangingConfigs(resources, id));
+ final ConstantState<StateListAnimator> constantState = animator
+ .createConstantState();
+ if (constantState != null) {
+ cache.put(id, theme, constantState);
+ // return a clone so that the animator in constant state is never used.
+ animator = constantState.newInstance(resources, theme);
+ }
+ }
+ return animator;
} catch (XmlPullParserException ex) {
Resources.NotFoundException rnf =
new Resources.NotFoundException(
@@ -172,14 +221,13 @@ public class AnimatorInflater {
for (int i = 0; i < attributeCount; i++) {
int attrName = attributeSet.getAttributeNameResource(i);
if (attrName == R.attr.animation) {
- animator = loadAnimator(context,
- attributeSet.getAttributeResourceValue(i, 0));
+ final int animId = attributeSet.getAttributeResourceValue(i, 0);
+ animator = loadAnimator(context, animId);
} else {
states[stateIndex++] =
attributeSet.getAttributeBooleanValue(i, false) ?
attrName : -attrName;
}
-
}
if (animator == null) {
animator = createAnimatorFromXml(context.getResources(),
@@ -192,7 +240,6 @@ public class AnimatorInflater {
}
stateListAnimator
.addState(StateSet.trimStateSet(states, stateIndex), animator);
-
}
break;
}
@@ -508,7 +555,6 @@ public class AnimatorInflater {
private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser,
AttributeSet attrs, AnimatorSet parent, int sequenceOrdering, float pixelSize)
throws XmlPullParserException, IOException {
-
Animator anim = null;
ArrayList<Animator> childAnims = null;
@@ -537,8 +583,8 @@ public class AnimatorInflater {
} else {
a = res.obtainAttributes(attrs, R.styleable.AnimatorSet);
}
- int ordering = a.getInt(R.styleable.AnimatorSet_ordering,
- TOGETHER);
+ anim.appendChangingConfigurations(a.getChangingConfigurations());
+ int ordering = a.getInt(R.styleable.AnimatorSet_ordering, TOGETHER);
createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering,
pixelSize);
a.recycle();
@@ -565,7 +611,6 @@ public class AnimatorInflater {
parent.playSequentially(animsArray);
}
}
-
return anim;
}
@@ -591,7 +636,6 @@ public class AnimatorInflater {
private static ValueAnimator loadAnimator(Resources res, Theme theme,
AttributeSet attrs, ValueAnimator anim, float pathErrorScale)
throws NotFoundException {
-
TypedArray arrayAnimator = null;
TypedArray arrayObjectAnimator = null;
@@ -609,25 +653,37 @@ public class AnimatorInflater {
} else {
arrayObjectAnimator = res.obtainAttributes(attrs, R.styleable.PropertyAnimator);
}
+ anim.appendChangingConfigurations(arrayObjectAnimator.getChangingConfigurations());
}
if (anim == null) {
anim = new ValueAnimator();
}
+ anim.appendChangingConfigurations(arrayAnimator.getChangingConfigurations());
parseAnimatorFromTypeArray(anim, arrayAnimator, arrayObjectAnimator, pathErrorScale);
- final int resID =
- arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0);
+ final int resID = arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0);
if (resID > 0) {
- anim.setInterpolator(AnimationUtils.loadInterpolator(res, theme, resID));
+ final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID);
+ if (interpolator instanceof BaseInterpolator) {
+ anim.appendChangingConfigurations(
+ ((BaseInterpolator) interpolator).getChangingConfiguration());
+ }
+ anim.setInterpolator(interpolator);
}
arrayAnimator.recycle();
if (arrayObjectAnimator != null) {
arrayObjectAnimator.recycle();
}
-
return anim;
}
+
+ private static int getChangingConfigs(Resources resources, int id) {
+ synchronized (sTmpTypedValue) {
+ resources.getValue(id, sTmpTypedValue, true);
+ return sTmpTypedValue.changingConfigurations;
+ }
+ }
}
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 0aa8fdd..92762c3 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -241,6 +241,19 @@ public final class AnimatorSet extends Animator {
}
/**
+ * @hide
+ */
+ @Override
+ public int getChangingConfigurations() {
+ int conf = super.getChangingConfigurations();
+ final int nodeCount = mNodes.size();
+ for (int i = 0; i < nodeCount; i ++) {
+ conf |= mNodes.get(i).animation.getChangingConfigurations();
+ }
+ return conf;
+ }
+
+ /**
* Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations}
* of this AnimatorSet. The default value is null, which means that no interpolator
* is set on this AnimatorSet. Setting the interpolator to any non-null value
@@ -628,23 +641,25 @@ public final class AnimatorSet extends Animator {
* manually, as we clone each Node (and its animation). The clone will then be sorted,
* and will populate any appropriate lists, when it is started.
*/
+ final int nodeCount = mNodes.size();
anim.mNeedsSort = true;
anim.mTerminated = false;
anim.mStarted = false;
anim.mPlayingSet = new ArrayList<Animator>();
anim.mNodeMap = new HashMap<Animator, Node>();
- anim.mNodes = new ArrayList<Node>();
- anim.mSortedNodes = new ArrayList<Node>();
+ anim.mNodes = new ArrayList<Node>(nodeCount);
+ anim.mSortedNodes = new ArrayList<Node>(nodeCount);
anim.mReversible = mReversible;
anim.mSetListener = null;
// Walk through the old nodes list, cloning each node and adding it to the new nodemap.
// One problem is that the old node dependencies point to nodes in the old AnimatorSet.
// We need to track the old/new nodes in order to reconstruct the dependencies in the clone.
- HashMap<Node, Node> nodeCloneMap = new HashMap<Node, Node>(); // <old, new>
- for (Node node : mNodes) {
+
+ for (int n = 0; n < nodeCount; n++) {
+ final Node node = mNodes.get(n);
Node nodeClone = node.clone();
- nodeCloneMap.put(node, nodeClone);
+ node.mTmpClone = nodeClone;
anim.mNodes.add(nodeClone);
anim.mNodeMap.put(nodeClone.animation, nodeClone);
// Clear out the dependencies in the clone; we'll set these up manually later
@@ -652,40 +667,50 @@ public final class AnimatorSet extends Animator {
nodeClone.tmpDependencies = null;
nodeClone.nodeDependents = null;
nodeClone.nodeDependencies = null;
+
// clear out any listeners that were set up by the AnimatorSet; these will
// be set up when the clone's nodes are sorted
- ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners();
+ final ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners();
if (cloneListeners != null) {
- ArrayList<AnimatorListener> listenersToRemove = null;
- for (AnimatorListener listener : cloneListeners) {
+ for (int i = cloneListeners.size() - 1; i >= 0; i--) {
+ final AnimatorListener listener = cloneListeners.get(i);
if (listener instanceof AnimatorSetListener) {
- if (listenersToRemove == null) {
- listenersToRemove = new ArrayList<AnimatorListener>();
- }
- listenersToRemove.add(listener);
- }
- }
- if (listenersToRemove != null) {
- for (AnimatorListener listener : listenersToRemove) {
- cloneListeners.remove(listener);
+ cloneListeners.remove(i);
}
}
}
}
// Now that we've cloned all of the nodes, we're ready to walk through their
// dependencies, mapping the old dependencies to the new nodes
- for (Node node : mNodes) {
- Node nodeClone = nodeCloneMap.get(node);
+ for (int n = 0; n < nodeCount; n++) {
+ final Node node = mNodes.get(n);
+ final Node clone = node.mTmpClone;
if (node.dependencies != null) {
- for (Dependency dependency : node.dependencies) {
- Node clonedDependencyNode = nodeCloneMap.get(dependency.node);
- Dependency cloneDependency = new Dependency(clonedDependencyNode,
+ clone.dependencies = new ArrayList<Dependency>(node.dependencies.size());
+ final int depSize = node.dependencies.size();
+ for (int i = 0; i < depSize; i ++) {
+ final Dependency dependency = node.dependencies.get(i);
+ Dependency cloneDependency = new Dependency(dependency.node.mTmpClone,
dependency.rule);
- nodeClone.addDependency(cloneDependency);
+ clone.dependencies.add(cloneDependency);
+ }
+ }
+ if (node.nodeDependents != null) {
+ clone.nodeDependents = new ArrayList<Node>(node.nodeDependents.size());
+ for (Node dep : node.nodeDependents) {
+ clone.nodeDependents.add(dep.mTmpClone);
+ }
+ }
+ if (node.nodeDependencies != null) {
+ clone.nodeDependencies = new ArrayList<Node>(node.nodeDependencies.size());
+ for (Node dep : node.nodeDependencies) {
+ clone.nodeDependencies.add(dep.mTmpClone);
}
}
}
-
+ for (int n = 0; n < nodeCount; n++) {
+ mNodes.get(n).mTmpClone = null;
+ }
return anim;
}
@@ -1017,6 +1042,11 @@ public final class AnimatorSet extends Animator {
public boolean done = false;
/**
+ * Temporary field to hold the clone in AnimatorSet#clone. Cleaned after clone is complete
+ */
+ private Node mTmpClone = null;
+
+ /**
* Constructs the Node with the animation that it encapsulates. A Node has no
* dependencies by default; dependencies are added via the addDependency()
* method.
diff --git a/core/java/android/animation/FloatKeyframeSet.java b/core/java/android/animation/FloatKeyframeSet.java
index 12e5862..abac246 100644
--- a/core/java/android/animation/FloatKeyframeSet.java
+++ b/core/java/android/animation/FloatKeyframeSet.java
@@ -19,6 +19,7 @@ package android.animation;
import android.animation.Keyframe.FloatKeyframe;
import java.util.ArrayList;
+import java.util.List;
/**
* This class holds a collection of FloatKeyframe objects and is called by ValueAnimator to calculate
@@ -47,8 +48,8 @@ class FloatKeyframeSet extends KeyframeSet implements Keyframes.FloatKeyframes {
@Override
public FloatKeyframeSet clone() {
- ArrayList<Keyframe> keyframes = mKeyframes;
- int numKeyframes = mKeyframes.size();
+ final List<Keyframe> keyframes = mKeyframes;
+ final int numKeyframes = mKeyframes.size();
FloatKeyframe[] newKeyframes = new FloatKeyframe[numKeyframes];
for (int i = 0; i < numKeyframes; ++i) {
newKeyframes[i] = (FloatKeyframe) keyframes.get(i).clone();
diff --git a/core/java/android/animation/IntKeyframeSet.java b/core/java/android/animation/IntKeyframeSet.java
index 7a5b0ec..0ec5138 100644
--- a/core/java/android/animation/IntKeyframeSet.java
+++ b/core/java/android/animation/IntKeyframeSet.java
@@ -19,6 +19,7 @@ package android.animation;
import android.animation.Keyframe.IntKeyframe;
import java.util.ArrayList;
+import java.util.List;
/**
* This class holds a collection of IntKeyframe objects and is called by ValueAnimator to calculate
@@ -47,7 +48,7 @@ class IntKeyframeSet extends KeyframeSet implements Keyframes.IntKeyframes {
@Override
public IntKeyframeSet clone() {
- ArrayList<Keyframe> keyframes = mKeyframes;
+ List<Keyframe> keyframes = mKeyframes;
int numKeyframes = mKeyframes.size();
IntKeyframe[] newKeyframes = new IntKeyframe[numKeyframes];
for (int i = 0; i < numKeyframes; ++i) {
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
index 8d15db2..0e99bff 100644
--- a/core/java/android/animation/KeyframeSet.java
+++ b/core/java/android/animation/KeyframeSet.java
@@ -18,6 +18,8 @@ package android.animation;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+
import android.animation.Keyframe.IntKeyframe;
import android.animation.Keyframe.FloatKeyframe;
import android.animation.Keyframe.ObjectKeyframe;
@@ -36,16 +38,16 @@ class KeyframeSet implements Keyframes {
Keyframe mFirstKeyframe;
Keyframe mLastKeyframe;
TimeInterpolator mInterpolator; // only used in the 2-keyframe case
- ArrayList<Keyframe> mKeyframes; // only used when there are not 2 keyframes
+ List<Keyframe> mKeyframes; // only used when there are not 2 keyframes
TypeEvaluator mEvaluator;
public KeyframeSet(Keyframe... keyframes) {
mNumKeyframes = keyframes.length;
- mKeyframes = new ArrayList<Keyframe>();
- mKeyframes.addAll(Arrays.asList(keyframes));
- mFirstKeyframe = mKeyframes.get(0);
- mLastKeyframe = mKeyframes.get(mNumKeyframes - 1);
+ // immutable list
+ mKeyframes = Arrays.asList(keyframes);
+ mFirstKeyframe = keyframes[0];
+ mLastKeyframe = keyframes[mNumKeyframes - 1];
mInterpolator = mLastKeyframe.getInterpolator();
}
@@ -57,7 +59,7 @@ class KeyframeSet implements Keyframes {
public void invalidateCache() {
}
- public ArrayList<Keyframe> getKeyframes() {
+ public List<Keyframe> getKeyframes() {
return mKeyframes;
}
@@ -177,9 +179,9 @@ class KeyframeSet implements Keyframes {
@Override
public KeyframeSet clone() {
- ArrayList<Keyframe> keyframes = mKeyframes;
+ List<Keyframe> keyframes = mKeyframes;
int numKeyframes = mKeyframes.size();
- Keyframe[] newKeyframes = new Keyframe[numKeyframes];
+ final Keyframe[] newKeyframes = new Keyframe[numKeyframes];
for (int i = 0; i < numKeyframes; ++i) {
newKeyframes[i] = keyframes.get(i).clone();
}
diff --git a/core/java/android/animation/Keyframes.java b/core/java/android/animation/Keyframes.java
index 6611c6c..c921466 100644
--- a/core/java/android/animation/Keyframes.java
+++ b/core/java/android/animation/Keyframes.java
@@ -16,6 +16,7 @@
package android.animation;
import java.util.ArrayList;
+import java.util.List;
/**
* This interface abstracts a collection of Keyframe objects and is called by
@@ -62,7 +63,7 @@ interface Keyframes extends Cloneable {
* @return A list of all Keyframes contained by this. This may return null if this is
* not made up of Keyframes.
*/
- ArrayList<Keyframe> getKeyframes();
+ List<Keyframe> getKeyframes();
Keyframes clone();
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 500634c..59daaab 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -885,7 +885,8 @@ public final class ObjectAnimator extends ValueAnimator {
}
/**
- * Sets the target object whose property will be animated by this animation
+ * Sets the target object whose property will be animated by this animation. If the
+ * animator has been started, it will be canceled.
*
* @param target The object being animated
*/
@@ -893,6 +894,9 @@ public final class ObjectAnimator extends ValueAnimator {
public void setTarget(@Nullable Object target) {
final Object oldTarget = getTarget();
if (oldTarget != target) {
+ if (isStarted()) {
+ cancel();
+ }
mTarget = target == null ? null : new WeakReference<Object>(target);
// New target should cause re-initialization prior to starting
mInitialized = false;
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index d372933..bd7bca0 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -27,6 +27,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
@@ -104,10 +105,6 @@ public class PropertyValuesHolder implements Cloneable {
private static final HashMap<Class, HashMap<String, Method>> sGetterPropertyMap =
new HashMap<Class, HashMap<String, Method>>();
- // This lock is used to ensure that only one thread is accessing the property maps
- // at a time.
- final ReentrantReadWriteLock mPropertyMapLock = new ReentrantReadWriteLock();
-
// Used to pass single value to varargs parameter in setter invocation
final Object[] mTmpValueArray = new Object[1];
@@ -736,16 +733,19 @@ public class PropertyValuesHolder implements Cloneable {
HashMap<Class, HashMap<String, Method>> propertyMapMap,
String prefix, Class valueType) {
Method setterOrGetter = null;
- try {
+ synchronized(propertyMapMap) {
// Have to lock property map prior to reading it, to guard against
// another thread putting something in there after we've checked it
// but before we've added an entry to it
- mPropertyMapLock.writeLock().lock();
HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
+ boolean wasInMap = false;
if (propertyMap != null) {
- setterOrGetter = propertyMap.get(mPropertyName);
+ wasInMap = propertyMap.containsKey(mPropertyName);
+ if (wasInMap) {
+ setterOrGetter = propertyMap.get(mPropertyName);
+ }
}
- if (setterOrGetter == null) {
+ if (!wasInMap) {
setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
if (propertyMap == null) {
propertyMap = new HashMap<String, Method>();
@@ -753,8 +753,6 @@ public class PropertyValuesHolder implements Cloneable {
}
propertyMap.put(mPropertyName, setterOrGetter);
}
- } finally {
- mPropertyMapLock.writeLock().unlock();
}
return setterOrGetter;
}
@@ -791,7 +789,7 @@ public class PropertyValuesHolder implements Cloneable {
// check to make sure that mProperty is on the class of target
try {
Object testValue = null;
- ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
int keyframeCount = keyframes == null ? 0 : keyframes.size();
for (int i = 0; i < keyframeCount; i++) {
Keyframe kf = keyframes.get(i);
@@ -810,30 +808,33 @@ public class PropertyValuesHolder implements Cloneable {
mProperty = null;
}
}
- Class targetClass = target.getClass();
- if (mSetter == null) {
- setupSetter(targetClass);
- }
- ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
- int keyframeCount = keyframes == null ? 0 : keyframes.size();
- for (int i = 0; i < keyframeCount; i++) {
- Keyframe kf = keyframes.get(i);
- if (!kf.hasValue() || kf.valueWasSetOnStart()) {
- if (mGetter == null) {
- setupGetter(targetClass);
+ // We can't just say 'else' here because the catch statement sets mProperty to null.
+ if (mProperty == null) {
+ Class targetClass = target.getClass();
+ if (mSetter == null) {
+ setupSetter(targetClass);
+ }
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
+ int keyframeCount = keyframes == null ? 0 : keyframes.size();
+ for (int i = 0; i < keyframeCount; i++) {
+ Keyframe kf = keyframes.get(i);
+ if (!kf.hasValue() || kf.valueWasSetOnStart()) {
if (mGetter == null) {
- // Already logged the error - just return to avoid NPE
- return;
+ setupGetter(targetClass);
+ if (mGetter == null) {
+ // Already logged the error - just return to avoid NPE
+ return;
+ }
+ }
+ try {
+ Object value = convertBack(mGetter.invoke(target));
+ kf.setValue(value);
+ kf.setValueWasSetOnStart(true);
+ } catch (InvocationTargetException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ } catch (IllegalAccessException e) {
+ Log.e("PropertyValuesHolder", e.toString());
}
- }
- try {
- Object value = convertBack(mGetter.invoke(target));
- kf.setValue(value);
- kf.setValueWasSetOnStart(true);
- } catch (InvocationTargetException e) {
- Log.e("PropertyValuesHolder", e.toString());
- } catch (IllegalAccessException e) {
- Log.e("PropertyValuesHolder", e.toString());
}
}
}
@@ -890,7 +891,7 @@ public class PropertyValuesHolder implements Cloneable {
* @param target The object which holds the start values that should be set.
*/
void setupStartValue(Object target) {
- ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
if (!keyframes.isEmpty()) {
setupValue(target, keyframes.get(0));
}
@@ -905,7 +906,7 @@ public class PropertyValuesHolder implements Cloneable {
* @param target The object which holds the start values that should be set.
*/
void setupEndValue(Object target) {
- ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
if (!keyframes.isEmpty()) {
setupValue(target, keyframes.get(keyframes.size() - 1));
}
@@ -1177,32 +1178,33 @@ public class PropertyValuesHolder implements Cloneable {
return;
}
// Check new static hashmap<propName, int> for setter method
- try {
- mPropertyMapLock.writeLock().lock();
+ synchronized(sJNISetterPropertyMap) {
HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ boolean wasInMap = false;
if (propertyMap != null) {
- Long jniSetter = propertyMap.get(mPropertyName);
- if (jniSetter != null) {
- mJniSetter = jniSetter;
+ wasInMap = propertyMap.containsKey(mPropertyName);
+ if (wasInMap) {
+ Long jniSetter = propertyMap.get(mPropertyName);
+ if (jniSetter != null) {
+ mJniSetter = jniSetter;
+ }
}
}
- if (mJniSetter == 0) {
+ if (!wasInMap) {
String methodName = getMethodName("set", mPropertyName);
- mJniSetter = nGetIntMethod(targetClass, methodName);
- if (mJniSetter != 0) {
- if (propertyMap == null) {
- propertyMap = new HashMap<String, Long>();
- sJNISetterPropertyMap.put(targetClass, propertyMap);
- }
- propertyMap.put(mPropertyName, mJniSetter);
+ try {
+ mJniSetter = nGetIntMethod(targetClass, methodName);
+ } catch (NoSuchMethodError e) {
+ // Couldn't find it via JNI - try reflection next. Probably means the method
+ // doesn't exist, or the type is wrong. An error will be logged later if
+ // reflection fails as well.
+ }
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Long>();
+ sJNISetterPropertyMap.put(targetClass, propertyMap);
}
+ propertyMap.put(mPropertyName, mJniSetter);
}
- } catch (NoSuchMethodError e) {
- // Couldn't find it via JNI - try reflection next. Probably means the method
- // doesn't exist, or the type is wrong. An error will be logged later if
- // reflection fails as well.
- } finally {
- mPropertyMapLock.writeLock().unlock();
}
if (mJniSetter == 0) {
// Couldn't find method through fast JNI approach - just use reflection
@@ -1314,32 +1316,33 @@ public class PropertyValuesHolder implements Cloneable {
return;
}
// Check new static hashmap<propName, int> for setter method
- try {
- mPropertyMapLock.writeLock().lock();
+ synchronized (sJNISetterPropertyMap) {
HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ boolean wasInMap = false;
if (propertyMap != null) {
- Long jniSetter = propertyMap.get(mPropertyName);
- if (jniSetter != null) {
- mJniSetter = jniSetter;
+ wasInMap = propertyMap.containsKey(mPropertyName);
+ if (wasInMap) {
+ Long jniSetter = propertyMap.get(mPropertyName);
+ if (jniSetter != null) {
+ mJniSetter = jniSetter;
+ }
}
}
- if (mJniSetter == 0) {
+ if (!wasInMap) {
String methodName = getMethodName("set", mPropertyName);
- mJniSetter = nGetFloatMethod(targetClass, methodName);
- if (mJniSetter != 0) {
- if (propertyMap == null) {
- propertyMap = new HashMap<String, Long>();
- sJNISetterPropertyMap.put(targetClass, propertyMap);
- }
- propertyMap.put(mPropertyName, mJniSetter);
+ try {
+ mJniSetter = nGetFloatMethod(targetClass, methodName);
+ } catch (NoSuchMethodError e) {
+ // Couldn't find it via JNI - try reflection next. Probably means the method
+ // doesn't exist, or the type is wrong. An error will be logged later if
+ // reflection fails as well.
+ }
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Long>();
+ sJNISetterPropertyMap.put(targetClass, propertyMap);
}
+ propertyMap.put(mPropertyName, mJniSetter);
}
- } catch (NoSuchMethodError e) {
- // Couldn't find it via JNI - try reflection next. Probably means the method
- // doesn't exist, or the type is wrong. An error will be logged later if
- // reflection fails as well.
- } finally {
- mPropertyMapLock.writeLock().unlock();
}
if (mJniSetter == 0) {
// Couldn't find method through fast JNI approach - just use reflection
@@ -1418,16 +1421,19 @@ public class PropertyValuesHolder implements Cloneable {
if (mJniSetter != 0) {
return;
}
- try {
- mPropertyMapLock.writeLock().lock();
+ synchronized(sJNISetterPropertyMap) {
HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ boolean wasInMap = false;
if (propertyMap != null) {
- Long jniSetterLong = propertyMap.get(mPropertyName);
- if (jniSetterLong != null) {
- mJniSetter = jniSetterLong;
+ wasInMap = propertyMap.containsKey(mPropertyName);
+ if (wasInMap) {
+ Long jniSetter = propertyMap.get(mPropertyName);
+ if (jniSetter != null) {
+ mJniSetter = jniSetter;
+ }
}
}
- if (mJniSetter == 0) {
+ if (!wasInMap) {
String methodName = getMethodName("set", mPropertyName);
calculateValue(0f);
float[] values = (float[]) getAnimatedValue();
@@ -1436,19 +1442,20 @@ public class PropertyValuesHolder implements Cloneable {
mJniSetter = nGetMultipleFloatMethod(targetClass, methodName, numParams);
} catch (NoSuchMethodError e) {
// try without the 'set' prefix
- mJniSetter = nGetMultipleFloatMethod(targetClass, mPropertyName, numParams);
- }
- if (mJniSetter != 0) {
- if (propertyMap == null) {
- propertyMap = new HashMap<String, Long>();
- sJNISetterPropertyMap.put(targetClass, propertyMap);
+ try {
+ mJniSetter = nGetMultipleFloatMethod(targetClass, mPropertyName,
+ numParams);
+ } catch (NoSuchMethodError e2) {
+ // just try reflection next
}
- propertyMap.put(mPropertyName, mJniSetter);
}
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Long>();
+ sJNISetterPropertyMap.put(targetClass, propertyMap);
+ }
+ propertyMap.put(mPropertyName, mJniSetter);
}
- } finally {
- mPropertyMapLock.writeLock().unlock();
- }
+ }
}
}
@@ -1521,16 +1528,19 @@ public class PropertyValuesHolder implements Cloneable {
if (mJniSetter != 0) {
return;
}
- try {
- mPropertyMapLock.writeLock().lock();
+ synchronized(sJNISetterPropertyMap) {
HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ boolean wasInMap = false;
if (propertyMap != null) {
- Long jniSetterLong = propertyMap.get(mPropertyName);
- if (jniSetterLong != null) {
- mJniSetter = jniSetterLong;
+ wasInMap = propertyMap.containsKey(mPropertyName);
+ if (wasInMap) {
+ Long jniSetter = propertyMap.get(mPropertyName);
+ if (jniSetter != null) {
+ mJniSetter = jniSetter;
+ }
}
}
- if (mJniSetter == 0) {
+ if (!wasInMap) {
String methodName = getMethodName("set", mPropertyName);
calculateValue(0f);
int[] values = (int[]) getAnimatedValue();
@@ -1539,18 +1549,19 @@ public class PropertyValuesHolder implements Cloneable {
mJniSetter = nGetMultipleIntMethod(targetClass, methodName, numParams);
} catch (NoSuchMethodError e) {
// try without the 'set' prefix
- mJniSetter = nGetMultipleIntMethod(targetClass, mPropertyName, numParams);
- }
- if (mJniSetter != 0) {
- if (propertyMap == null) {
- propertyMap = new HashMap<String, Long>();
- sJNISetterPropertyMap.put(targetClass, propertyMap);
+ try {
+ mJniSetter = nGetMultipleIntMethod(targetClass, mPropertyName,
+ numParams);
+ } catch (NoSuchMethodError e2) {
+ // couldn't find it.
}
- propertyMap.put(mPropertyName, mJniSetter);
}
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Long>();
+ sJNISetterPropertyMap.put(targetClass, propertyMap);
+ }
+ propertyMap.put(mPropertyName, mJniSetter);
}
- } finally {
- mPropertyMapLock.writeLock().unlock();
}
}
}
diff --git a/core/java/android/animation/StateListAnimator.java b/core/java/android/animation/StateListAnimator.java
index 7256a06..d49e914 100644
--- a/core/java/android/animation/StateListAnimator.java
+++ b/core/java/android/animation/StateListAnimator.java
@@ -16,6 +16,7 @@
package android.animation;
+import android.content.res.ConstantState;
import android.util.StateSet;
import android.view.View;
@@ -44,25 +45,31 @@ import java.util.ArrayList;
* @attr ref android.R.styleable#DrawableStates_state_pressed
* @attr ref android.R.styleable#StateListAnimatorItem_animation
*/
-public class StateListAnimator {
-
- private final ArrayList<Tuple> mTuples = new ArrayList<Tuple>();
+public class StateListAnimator implements Cloneable {
+ private ArrayList<Tuple> mTuples = new ArrayList<Tuple>();
private Tuple mLastMatch = null;
-
private Animator mRunningAnimator = null;
-
private WeakReference<View> mViewRef;
+ private StateListAnimatorConstantState mConstantState;
+ private AnimatorListenerAdapter mAnimatorListener;
+ private int mChangingConfigurations;
- private AnimatorListenerAdapter mAnimatorListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- animation.setTarget(null);
- if (mRunningAnimator == animation) {
- mRunningAnimator = null;
+ public StateListAnimator() {
+ initAnimatorListener();
+ }
+
+ private void initAnimatorListener() {
+ mAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animation.setTarget(null);
+ if (mRunningAnimator == animation) {
+ mRunningAnimator = null;
+ }
}
- }
- };
+ };
+ }
/**
* Associates the given animator with the provided drawable state specs so that it will be run
@@ -75,6 +82,7 @@ public class StateListAnimator {
Tuple tuple = new Tuple(specs, animator);
tuple.mAnimator.addListener(mAnimatorListener);
mTuples.add(tuple);
+ mChangingConfigurations |= animator.getChangingConfigurations();
}
/**
@@ -118,12 +126,35 @@ public class StateListAnimator {
for (int i = 0; i < size; i++) {
mTuples.get(i).mAnimator.setTarget(null);
}
-
mViewRef = null;
mLastMatch = null;
mRunningAnimator = null;
}
+ @Override
+ public StateListAnimator clone() {
+ try {
+ StateListAnimator clone = (StateListAnimator) super.clone();
+ clone.mTuples = new ArrayList<Tuple>(mTuples.size());
+ clone.mLastMatch = null;
+ clone.mRunningAnimator = null;
+ clone.mViewRef = null;
+ clone.mAnimatorListener = null;
+ clone.initAnimatorListener();
+ final int tupleSize = mTuples.size();
+ for (int i = 0; i < tupleSize; i++) {
+ final Tuple tuple = mTuples.get(i);
+ final Animator animatorClone = tuple.mAnimator.clone();
+ animatorClone.removeListener(mAnimatorListener);
+ clone.addState(tuple.mSpecs, animatorClone);
+ }
+ clone.setChangingConfigurations(getChangingConfigurations());
+ return clone;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError("cannot clone state list animator", e);
+ }
+ }
+
/**
* Called by View
* @hide
@@ -182,6 +213,63 @@ public class StateListAnimator {
}
/**
+ * Return a mask of the configuration parameters for which this animator may change, requiring
+ * that it be re-created. The default implementation returns whatever was provided through
+ * {@link #setChangingConfigurations(int)} or 0 by default.
+ *
+ * @return Returns a mask of the changing configuration parameters, as defined by
+ * {@link android.content.pm.ActivityInfo}.
+ *
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
+ /**
+ * Set a mask of the configuration parameters for which this animator may change, requiring
+ * that it should be recreated from resources instead of being cloned.
+ *
+ * @param configs A mask of the changing configuration parameters, as
+ * defined by {@link android.content.pm.ActivityInfo}.
+ *
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public void setChangingConfigurations(int configs) {
+ mChangingConfigurations = configs;
+ }
+
+ /**
+ * Sets the changing configurations value to the union of the current changing configurations
+ * and the provided configs.
+ * This method is called while loading the animator.
+ * @hide
+ */
+ public void appendChangingConfigurations(int configs) {
+ mChangingConfigurations |= configs;
+ }
+
+ /**
+ * Return a {@link android.content.res.ConstantState} instance that holds the shared state of
+ * this Animator.
+ * <p>
+ * This constant state is used to create new instances of this animator when needed. Default
+ * implementation creates a new {@link StateListAnimatorConstantState}. You can override this
+ * method to provide your custom logic or return null if you don't want this animator to be
+ * cached.
+ *
+ * @return The {@link android.content.res.ConstantState} associated to this Animator.
+ * @see android.content.res.ConstantState
+ * @see #clone()
+ * @hide
+ */
+ public ConstantState<StateListAnimator> createConstantState() {
+ return new StateListAnimatorConstantState(this);
+ }
+
+ /**
* @hide
*/
public static class Tuple {
@@ -209,4 +297,36 @@ public class StateListAnimator {
return mAnimator;
}
}
+
+ /**
+ * Creates a constant state which holds changing configurations information associated with the
+ * given Animator.
+ * <p>
+ * When new instance is called, default implementation clones the Animator.
+ */
+ private static class StateListAnimatorConstantState
+ extends ConstantState<StateListAnimator> {
+
+ final StateListAnimator mAnimator;
+
+ int mChangingConf;
+
+ public StateListAnimatorConstantState(StateListAnimator animator) {
+ mAnimator = animator;
+ mAnimator.mConstantState = this;
+ mChangingConf = mAnimator.getChangingConfigurations();
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mChangingConf;
+ }
+
+ @Override
+ public StateListAnimator newInstance() {
+ final StateListAnimator clone = mAnimator.clone();
+ clone.mConstantState = this;
+ return clone;
+ }
+ }
}
diff --git a/core/java/android/animation/TimeAnimator.java b/core/java/android/animation/TimeAnimator.java
index f9aa00e..1738ade 100644
--- a/core/java/android/animation/TimeAnimator.java
+++ b/core/java/android/animation/TimeAnimator.java
@@ -1,5 +1,23 @@
+/*
+ * 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.animation;
+import android.view.animation.AnimationUtils;
+
/**
* This class provides a simple callback mechanism to listeners that is synchronized with all
* other animators in the system. There is no duration, interpolation, or object value-setting
@@ -29,6 +47,13 @@ public class TimeAnimator extends ValueAnimator {
return false;
}
+ @Override
+ public void setCurrentPlayTime(long playTime) {
+ long currentTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartTime = Math.max(mStartTime, currentTime - playTime);
+ animationFrame(currentTime);
+ }
+
/**
* Sets a listener that is sent update events throughout the life of
* an animation.
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 0d17d67..9709555 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -16,6 +16,7 @@
package android.animation;
+import android.content.res.ConfigurationBoundResourceCache;
import android.os.Looper;
import android.os.Trace;
import android.util.AndroidRuntimeException;
@@ -78,7 +79,7 @@ public class ValueAnimator extends Animator {
* Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked
* to a value.
*/
- long mSeekTime = -1;
+ float mSeekFraction = -1;
/**
* Set on the next frame after pause() is called, used to calculate a new startTime
@@ -113,6 +114,15 @@ public class ValueAnimator extends Animator {
private boolean mPlayingBackwards = false;
/**
+ * Flag to indicate whether this animator is playing in reverse mode, specifically
+ * by being started or interrupted by a call to reverse(). This flag is different than
+ * mPlayingBackwards, which indicates merely whether the current iteration of the
+ * animator is playing in reverse. It is used in corner cases to determine proper end
+ * behavior.
+ */
+ private boolean mReversing;
+
+ /**
* This variable tracks the current iteration that is playing. When mCurrentIteration exceeds the
* repeatCount (if repeatCount!=INFINITE), the animation ends
*/
@@ -536,14 +546,60 @@ public class ValueAnimator extends Animator {
* @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
*/
public void setCurrentPlayTime(long playTime) {
+ float fraction = mUnscaledDuration > 0 ? (float) playTime / mUnscaledDuration : 1;
+ setCurrentFraction(fraction);
+ }
+
+ /**
+ * Sets the position of the animation to the specified fraction. This fraction should
+ * be between 0 and the total fraction of the animation, including any repetition. That is,
+ * a fraction of 0 will position the animation at the beginning, a value of 1 at the end,
+ * and a value of 2 at the end of a reversing animator that repeats once. If
+ * the animation has not yet been started, then it will not advance forward after it is
+ * set to this fraction; it will simply set the fraction to this value and perform any
+ * appropriate actions based on that fraction. If the animation is already running, then
+ * setCurrentFraction() will set the current fraction to this value and continue
+ * playing from that point. {@link Animator.AnimatorListener} events are not called
+ * due to changing the fraction; those events are only processed while the animation
+ * is running.
+ *
+ * @param fraction The fraction to which the animation is advanced or rewound. Values
+ * outside the range of 0 to the maximum fraction for the animator will be clamped to
+ * the correct range.
+ */
+ public void setCurrentFraction(float fraction) {
initAnimation();
+ if (fraction < 0) {
+ fraction = 0;
+ }
+ int iteration = (int) fraction;
+ if (fraction == 1) {
+ iteration -= 1;
+ } else if (fraction > 1) {
+ if (iteration < (mRepeatCount + 1) || mRepeatCount == INFINITE) {
+ if (mRepeatMode == REVERSE) {
+ mPlayingBackwards = (iteration % 2) != 0;
+ }
+ fraction = fraction % 1f;
+ } else {
+ fraction = 1;
+ iteration -= 1;
+ }
+ } else {
+ mPlayingBackwards = mReversing;
+ }
+ mCurrentIteration = iteration;
+ long seekTime = (long) (mDuration * fraction);
long currentTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartTime = currentTime - seekTime;
if (mPlayingState != RUNNING) {
- mSeekTime = playTime;
+ mSeekFraction = fraction;
mPlayingState = SEEKED;
}
- mStartTime = currentTime - playTime;
- doAnimationFrame(currentTime);
+ if (mPlayingBackwards) {
+ fraction = 1f - fraction;
+ }
+ animateValue(fraction);
}
/**
@@ -945,8 +1001,31 @@ public class ValueAnimator extends Animator {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
+ mReversing = playBackwards;
mPlayingBackwards = playBackwards;
- mCurrentIteration = 0;
+ if (playBackwards && mSeekFraction != -1) {
+ if (mSeekFraction == 0 && mCurrentIteration == 0) {
+ // special case: reversing from seek-to-0 should act as if not seeked at all
+ mSeekFraction = 0;
+ } else if (mRepeatCount == INFINITE) {
+ mSeekFraction = 1 - (mSeekFraction % 1);
+ } else {
+ mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction);
+ }
+ mCurrentIteration = (int) mSeekFraction;
+ mSeekFraction = mSeekFraction % 1;
+ }
+ if (mCurrentIteration > 0 && mRepeatMode == REVERSE &&
+ (mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
+ // if we were seeked to some other iteration in a reversing animator,
+ // figure out the correct direction to start playing based on the iteration
+ if (playBackwards) {
+ mPlayingBackwards = (mCurrentIteration % 2) == 0;
+ } else {
+ mPlayingBackwards = (mCurrentIteration % 2) != 0;
+ }
+ }
+ int prevPlayingState = mPlayingState;
mPlayingState = STOPPED;
mStarted = true;
mStartedDelay = false;
@@ -956,7 +1035,9 @@ public class ValueAnimator extends Animator {
animationHandler.mPendingAnimations.add(this);
if (mStartDelay == 0) {
// This sets the initial value of the animation, prior to actually starting it running
- setCurrentPlayTime(0);
+ if (prevPlayingState != SEEKED) {
+ setCurrentPlayTime(0);
+ }
mPlayingState = STOPPED;
mRunning = true;
notifyStartListeners();
@@ -1051,6 +1132,7 @@ public class ValueAnimator extends Animator {
long currentPlayTime = currentTime - mStartTime;
long timeLeft = mDuration - currentPlayTime;
mStartTime = currentTime - timeLeft;
+ mReversing = !mReversing;
} else if (mStarted) {
end();
} else {
@@ -1093,6 +1175,8 @@ public class ValueAnimator extends Animator {
mStarted = false;
mStartListenersCalled = false;
mPlayingBackwards = false;
+ mReversing = false;
+ mCurrentIteration = 0;
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
System.identityHashCode(this));
@@ -1181,6 +1265,13 @@ public class ValueAnimator extends Animator {
case RUNNING:
case SEEKED:
float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
+ if (mDuration == 0 && mRepeatCount != INFINITE) {
+ // Skip to the end
+ mCurrentIteration = mRepeatCount;
+ if (!mReversing) {
+ mPlayingBackwards = false;
+ }
+ }
if (fraction >= 1f) {
if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
// Time to repeat
@@ -1193,7 +1284,7 @@ public class ValueAnimator extends Animator {
if (mRepeatMode == REVERSE) {
mPlayingBackwards = !mPlayingBackwards;
}
- mCurrentIteration += (int)fraction;
+ mCurrentIteration += (int) fraction;
fraction = fraction % 1f;
mStartTime += mDuration;
} else {
@@ -1220,12 +1311,12 @@ public class ValueAnimator extends Animator {
final boolean doAnimationFrame(long frameTime) {
if (mPlayingState == STOPPED) {
mPlayingState = RUNNING;
- if (mSeekTime < 0) {
+ if (mSeekFraction < 0) {
mStartTime = frameTime;
} else {
- mStartTime = frameTime - mSeekTime;
- // Now that we're playing, reset the seek time
- mSeekTime = -1;
+ long seekTime = (long) (mDuration * mSeekFraction);
+ mStartTime = frameTime - seekTime;
+ mSeekFraction = -1;
}
}
if (mPaused) {
@@ -1289,15 +1380,11 @@ public class ValueAnimator extends Animator {
public ValueAnimator clone() {
final ValueAnimator anim = (ValueAnimator) super.clone();
if (mUpdateListeners != null) {
- ArrayList<AnimatorUpdateListener> oldListeners = mUpdateListeners;
- anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
- int numListeners = oldListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- anim.mUpdateListeners.add(oldListeners.get(i));
- }
+ anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>(mUpdateListeners);
}
- anim.mSeekTime = -1;
+ anim.mSeekFraction = -1;
anim.mPlayingBackwards = false;
+ anim.mReversing = false;
anim.mCurrentIteration = 0;
anim.mInitialized = false;
anim.mPlayingState = STOPPED;
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4b705dd..9568897 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -693,6 +693,7 @@ public class Activity extends ContextThemeWrapper
/*package*/ String mEmbeddedID;
private Application mApplication;
/*package*/ Intent mIntent;
+ /*package*/ String mReferrer;
private ComponentName mComponent;
/*package*/ ActivityInfo mActivityInfo;
/*package*/ ActivityThread mMainThread;
@@ -2376,8 +2377,10 @@ public class Activity extends ContextThemeWrapper
if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
return false;
} else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
- if (getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL,
- keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
+ Window w = getWindow();
+ if (w.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
+ w.performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, keyCode, event,
+ Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
return true;
}
return false;
@@ -2942,7 +2945,8 @@ public class Activity extends ContextThemeWrapper
* time it needs to be displayed.
*/
public void invalidateOptionsMenu() {
- if (mActionBar == null || !mActionBar.invalidateOptionsMenu()) {
+ if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
+ (mActionBar == null || !mActionBar.invalidateOptionsMenu())) {
mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
}
}
@@ -3154,7 +3158,8 @@ public class Activity extends ContextThemeWrapper
* open, this method does nothing.
*/
public void openOptionsMenu() {
- if (mActionBar == null || !mActionBar.openOptionsMenu()) {
+ if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
+ (mActionBar == null || !mActionBar.openOptionsMenu())) {
mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
}
}
@@ -3164,7 +3169,9 @@ public class Activity extends ContextThemeWrapper
* closed, this method does nothing.
*/
public void closeOptionsMenu() {
- mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
+ if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL)) {
+ mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
+ }
}
/**
@@ -3223,7 +3230,9 @@ public class Activity extends ContextThemeWrapper
* Programmatically closes the most recently opened context menu, if showing.
*/
public void closeContextMenu() {
- mWindow.closePanel(Window.FEATURE_CONTEXT_MENU);
+ if (mWindow.hasFeature(Window.FEATURE_CONTEXT_MENU)) {
+ mWindow.closePanel(Window.FEATURE_CONTEXT_MENU);
+ }
}
/**
@@ -4448,6 +4457,39 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Return information about who launched this activity. If the launching Intent
+ * contains an {@link android.content.Intent#EXTRA_REFERRER Intent.EXTRA_REFERRER},
+ * that will be returned as-is; otherwise, if known, an
+ * {@link Intent#URI_ANDROID_APP_SCHEME android-app:} referrer URI containing the
+ * package name that started the Intent will be returned. This may return null if no
+ * referrer can be identified -- it is neither explicitly specified, nor is it known which
+ * application package was involved.
+ *
+ * <p>If called while inside the handling of {@link #onNewIntent}, this function will
+ * return the referrer that submitted that new intent to the activity. Otherwise, it
+ * always returns the referrer of the original Intent.</p>
+ *
+ * <p>Note that this is <em>not</em> a security feature -- you can not trust the
+ * referrer information, applications can spoof it.</p>
+ */
+ @Nullable
+ public Uri getReferrer() {
+ Intent intent = getIntent();
+ Uri referrer = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
+ if (referrer != null) {
+ return referrer;
+ }
+ String referrerName = intent.getStringExtra(Intent.EXTRA_REFERRER_NAME);
+ if (referrerName != null) {
+ return Uri.parse(referrerName);
+ }
+ if (mReferrer != null) {
+ return new Uri.Builder().scheme("android-app").authority(mReferrer).build();
+ }
+ return null;
+ }
+
+ /**
* Return the name of the package that invoked this activity. This is who
* the data in {@link #setResult setResult()} will be sent to. You can
* use this information to validate that the recipient is allowed to
@@ -4751,6 +4793,10 @@ public class Activity extends ContextThemeWrapper
* <p>You will receive this call immediately before onResume() when your
* activity is re-starting.
*
+ * <p>This method is never invoked if your activity sets
+ * {@link android.R.styleable#AndroidManifestActivity_noHistory noHistory} to
+ * <code>true</code>.
+ *
* @param requestCode The integer request code originally supplied to
* startActivityForResult(), allowing you to identify who this
* result came from.
@@ -5586,6 +5632,16 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * @hide
+ */
+ public void dispatchEnterAnimationComplete() {
+ onEnterAnimationComplete();
+ if (getWindow() != null && getWindow().getDecorView() != null) {
+ getWindow().getDecorView().getViewTreeObserver().dispatchOnEnterAnimationComplete();
+ }
+ }
+
+ /**
* Adjust the current immersive mode setting.
*
* Note that changing this value will have no effect on the activity's
@@ -5868,7 +5924,7 @@ public class Activity extends ContextThemeWrapper
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
- Configuration config, IVoiceInteractor voiceInteractor) {
+ Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
mFragments.attachActivity(this, mContainer, null);
@@ -5891,6 +5947,7 @@ public class Activity extends ContextThemeWrapper
mIdent = ident;
mApplication = application;
mIntent = intent;
+ mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 37e8aa4..7a636db 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -213,6 +213,13 @@ public class ActivityManager {
public static final int BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1;
/**
+ * Result for IActivityManager.broadcastIntent: trying to send a broadcast
+ * to a stopped user. Fail.
+ * @hide
+ */
+ public static final int BROADCAST_FAILED_USER_STOPPED = -2;
+
+ /**
* Type for IActivityManaqer.getIntentSender: this PendingIntent is
* for a sendBroadcast operation.
* @hide
@@ -1243,26 +1250,16 @@ public class ActivityManager {
}
/**
- * If set, the process of the root activity of the task will be killed
- * as part of removing the task.
- * @hide
- */
- public static final int REMOVE_TASK_KILL_PROCESS = 0x0001;
-
- /**
* Completely remove the given task.
*
* @param taskId Identifier of the task to be removed.
- * @param flags Additional operational flags. May be 0 or
- * {@link #REMOVE_TASK_KILL_PROCESS}.
* @return Returns true if the given task was found and removed.
*
* @hide
*/
- public boolean removeTask(int taskId, int flags)
- throws SecurityException {
+ public boolean removeTask(int taskId) throws SecurityException {
try {
- return ActivityManagerNative.getDefault().removeTask(taskId, flags);
+ return ActivityManagerNative.getDefault().removeTask(taskId);
} catch (RemoteException e) {
// System dead, we will be dead too soon!
return false;
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 2a17fa6..d56dc1e 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -23,8 +23,8 @@ package android.app;
*/
public abstract class ActivityManagerInternal {
// Called by the power manager.
- public abstract void goingToSleep();
- public abstract void wakingUp();
+ public abstract void onWakefulnessChanged(int wakefulness);
+
public abstract int startIsolatedProcess(String entryPoint, String[] mainArgs,
String processName, String abiOverride, int uid, Runnable crashHandler);
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 09d6c29..a7099d4 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -775,6 +775,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case REGISTER_TASK_STACK_LISTENER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ registerTaskStackListener(ITaskStackListener.Stub.asInterface(token));
+ reply.writeNoException();
+ return true;
+ }
+
case GET_TASK_FOR_ACTIVITY_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -1187,6 +1195,18 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case CHECK_PERMISSION_WITH_TOKEN_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String perm = data.readString();
+ int pid = data.readInt();
+ int uid = data.readInt();
+ IBinder token = data.readStrongBinder();
+ int res = checkPermissionWithToken(perm, pid, uid, token);
+ reply.writeNoException();
+ reply.writeInt(res);
+ return true;
+ }
+
case CHECK_URI_PERMISSION_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
Uri uri = Uri.CREATOR.createFromParcel(data);
@@ -1194,7 +1214,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
int uid = data.readInt();
int mode = data.readInt();
int userId = data.readInt();
- int res = checkUriPermission(uri, pid, uid, mode, userId);
+ IBinder callerToken = data.readStrongBinder();
+ int res = checkUriPermission(uri, pid, uid, mode, userId, callerToken);
reply.writeNoException();
reply.writeInt(res);
return true;
@@ -1885,8 +1906,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
{
data.enforceInterface(IActivityManager.descriptor);
int taskId = data.readInt();
- int fl = data.readInt();
- boolean result = removeTask(taskId, fl);
+ boolean result = removeTask(taskId);
reply.writeNoException();
reply.writeInt(result ? 1 : 0);
return true;
@@ -2281,6 +2301,20 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case START_IN_PLACE_ANIMATION_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final Bundle bundle;
+ if (data.readInt() == 0) {
+ bundle = null;
+ } else {
+ bundle = data.readBundle();
+ }
+ final ActivityOptions options = bundle == null ? null : new ActivityOptions(bundle);
+ startInPlaceAnimationOnFrontMostApplication(options);
+ reply.writeNoException();
+ return true;
+ }
+
case REQUEST_VISIBLE_BEHIND_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -2331,6 +2365,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case SYSTEM_BACKUP_RESTORED: {
+ data.enforceInterface(IActivityManager.descriptor);
+ systemBackupRestored();
+ reply.writeNoException();
+ return true;
+ }
+
case NOTIFY_CLEARTEXT_NETWORK_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
final int uid = data.readInt();
@@ -3252,6 +3293,18 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
+ @Override
+ public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(listener.asBinder());
+ mRemote.transact(REGISTER_TASK_STACK_LISTENER_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -3741,7 +3794,7 @@ class ActivityManagerProxy implements IActivityManager
mRemote.transact(GET_INTENT_SENDER_TRANSACTION, data, reply, 0);
reply.readException();
IIntentSender res = IIntentSender.Stub.asInterface(
- reply.readStrongBinder());
+ reply.readStrongBinder());
data.recycle();
reply.recycle();
return res;
@@ -3850,6 +3903,22 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return res;
}
+ public int checkPermissionWithToken(String permission, int pid, int uid, IBinder callerToken)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(permission);
+ data.writeInt(pid);
+ data.writeInt(uid);
+ data.writeStrongBinder(callerToken);
+ mRemote.transact(CHECK_PERMISSION_WITH_TOKEN_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int res = reply.readInt();
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
public boolean clearApplicationUserData(final String packageName,
final IPackageDataObserver observer, final int userId) throws RemoteException {
Parcel data = Parcel.obtain();
@@ -3865,8 +3934,8 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return res;
}
- public int checkUriPermission(Uri uri, int pid, int uid, int mode, int userId)
- throws RemoteException {
+ public int checkUriPermission(Uri uri, int pid, int uid, int mode, int userId,
+ IBinder callerToken) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3875,6 +3944,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeInt(uid);
data.writeInt(mode);
data.writeInt(userId);
+ data.writeStrongBinder(callerToken);
mRemote.transact(CHECK_URI_PERMISSION_TRANSACTION, data, reply, 0);
reply.readException();
int res = reply.readInt();
@@ -4790,12 +4860,11 @@ class ActivityManagerProxy implements IActivityManager
return result;
}
- public boolean removeTask(int taskId, int flags) throws RemoteException {
+ public boolean removeTask(int taskId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeInt(taskId);
- data.writeInt(flags);
mRemote.transact(REMOVE_TASK_TRANSACTION, data, reply, 0);
reply.readException();
boolean result = reply.readInt() != 0;
@@ -5312,6 +5381,24 @@ class ActivityManagerProxy implements IActivityManager
}
@Override
+ public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions options)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ if (options == null) {
+ data.writeInt(0);
+ } else {
+ data.writeInt(1);
+ data.writeBundle(options.toBundle());
+ }
+ mRemote.transact(START_IN_PLACE_ANIMATION_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
public boolean requestVisibleBehind(IBinder token, boolean visible) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -5391,6 +5478,17 @@ class ActivityManagerProxy implements IActivityManager
}
@Override
+ public void systemBackupRestored() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(SYSTEM_BACKUP_RESTORED, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
public void notifyCleartextNetwork(int uid, byte[] firstPacket) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index cd6a4f5..39ae65c 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -63,6 +63,12 @@ public class ActivityOptions {
public static final String KEY_ANIM_EXIT_RES_ID = "android:animExitRes";
/**
+ * Custom in-place animation resource ID.
+ * @hide
+ */
+ public static final String KEY_ANIM_IN_PLACE_RES_ID = "android:animInPlaceRes";
+
+ /**
* Bitmap for thumbnail animation.
* @hide
*/
@@ -132,11 +138,14 @@ public class ActivityOptions {
public static final int ANIM_THUMBNAIL_ASPECT_SCALE_UP = 8;
/** @hide */
public static final int ANIM_THUMBNAIL_ASPECT_SCALE_DOWN = 9;
+ /** @hide */
+ public static final int ANIM_CUSTOM_IN_PLACE = 10;
private String mPackageName;
private int mAnimationType = ANIM_NONE;
private int mCustomEnterResId;
private int mCustomExitResId;
+ private int mCustomInPlaceResId;
private Bitmap mThumbnail;
private int mStartX;
private int mStartY;
@@ -198,6 +207,30 @@ public class ActivityOptions {
return opts;
}
+ /**
+ * Creates an ActivityOptions specifying a custom animation to run in place on an existing
+ * activity.
+ *
+ * @param context Who is defining this. This is the application that the
+ * animation resources will be loaded from.
+ * @param animId A resource ID of the animation resource to use for
+ * the incoming activity.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when running an in-place animation.
+ * @hide
+ */
+ public static ActivityOptions makeCustomInPlaceAnimation(Context context, int animId) {
+ if (animId == 0) {
+ throw new RuntimeException("You must specify a valid animation.");
+ }
+
+ ActivityOptions opts = new ActivityOptions();
+ opts.mPackageName = context.getPackageName();
+ opts.mAnimationType = ANIM_CUSTOM_IN_PLACE;
+ opts.mCustomInPlaceResId = animId;
+ return opts;
+ }
+
private void setOnAnimationStartedListener(Handler handler,
OnAnimationStartedListener listener) {
if (listener != null) {
@@ -351,6 +384,8 @@ public class ActivityOptions {
* of the animation.
* @param startX The x starting location of the bitmap, relative to <var>source</var>.
* @param startY The y starting location of the bitmap, relative to <var>source</var>.
+ * @param handler If <var>listener</var> is non-null this must be a valid
+ * Handler on which to dispatch the callback; otherwise it should be null.
* @param listener Optional OnAnimationStartedListener to find out when the
* requested animation has started running. If for some reason the animation
* is not executed, the callback will happen immediately.
@@ -360,9 +395,9 @@ public class ActivityOptions {
*/
public static ActivityOptions makeThumbnailAspectScaleUpAnimation(View source,
Bitmap thumbnail, int startX, int startY, int targetWidth, int targetHeight,
- OnAnimationStartedListener listener) {
+ Handler handler, OnAnimationStartedListener listener) {
return makeAspectScaledThumbnailAnimation(source, thumbnail, startX, startY,
- targetWidth, targetHeight, listener, true);
+ targetWidth, targetHeight, handler, listener, true);
}
/**
@@ -375,6 +410,8 @@ public class ActivityOptions {
* of the animation.
* @param startX The x end location of the bitmap, relative to <var>source</var>.
* @param startY The y end location of the bitmap, relative to <var>source</var>.
+ * @param handler If <var>listener</var> is non-null this must be a valid
+ * Handler on which to dispatch the callback; otherwise it should be null.
* @param listener Optional OnAnimationStartedListener to find out when the
* requested animation has started running. If for some reason the animation
* is not executed, the callback will happen immediately.
@@ -384,14 +421,14 @@ public class ActivityOptions {
*/
public static ActivityOptions makeThumbnailAspectScaleDownAnimation(View source,
Bitmap thumbnail, int startX, int startY, int targetWidth, int targetHeight,
- OnAnimationStartedListener listener) {
+ Handler handler, OnAnimationStartedListener listener) {
return makeAspectScaledThumbnailAnimation(source, thumbnail, startX, startY,
- targetWidth, targetHeight, listener, false);
+ targetWidth, targetHeight, handler, listener, false);
}
private static ActivityOptions makeAspectScaledThumbnailAnimation(View source, Bitmap thumbnail,
int startX, int startY, int targetWidth, int targetHeight,
- OnAnimationStartedListener listener, boolean scaleUp) {
+ Handler handler, OnAnimationStartedListener listener, boolean scaleUp) {
ActivityOptions opts = new ActivityOptions();
opts.mPackageName = source.getContext().getPackageName();
opts.mAnimationType = scaleUp ? ANIM_THUMBNAIL_ASPECT_SCALE_UP :
@@ -403,7 +440,7 @@ public class ActivityOptions {
opts.mStartY = pts[1] + startY;
opts.mWidth = targetWidth;
opts.mHeight = targetHeight;
- opts.setOnAnimationStartedListener(source.getHandler(), listener);
+ opts.setOnAnimationStartedListener(handler, listener);
return opts;
}
@@ -540,6 +577,10 @@ public class ActivityOptions {
opts.getBinder(KEY_ANIM_START_LISTENER));
break;
+ case ANIM_CUSTOM_IN_PLACE:
+ mCustomInPlaceResId = opts.getInt(KEY_ANIM_IN_PLACE_RES_ID, 0);
+ break;
+
case ANIM_SCALE_UP:
mStartX = opts.getInt(KEY_ANIM_START_X, 0);
mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
@@ -592,6 +633,11 @@ public class ActivityOptions {
}
/** @hide */
+ public int getCustomInPlaceResId() {
+ return mCustomInPlaceResId;
+ }
+
+ /** @hide */
public Bitmap getThumbnail() {
return mThumbnail;
}
@@ -689,6 +735,9 @@ public class ActivityOptions {
}
mAnimationStartedListener = otherOptions.mAnimationStartedListener;
break;
+ case ANIM_CUSTOM_IN_PLACE:
+ mCustomInPlaceResId = otherOptions.mCustomInPlaceResId;
+ break;
case ANIM_SCALE_UP:
mStartX = otherOptions.mStartX;
mStartY = otherOptions.mStartY;
@@ -756,6 +805,9 @@ public class ActivityOptions {
b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
!= null ? mAnimationStartedListener.asBinder() : null);
break;
+ case ANIM_CUSTOM_IN_PLACE:
+ b.putInt(KEY_ANIM_IN_PLACE_RES_ID, mCustomInPlaceResId);
+ break;
case ANIM_SCALE_UP:
b.putInt(KEY_ANIM_START_X, mStartX);
b.putInt(KEY_ANIM_START_Y, mStartY);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index bffe395..6ea9f7e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -44,7 +44,10 @@ import android.database.sqlite.SQLiteDebug.DbStats;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.hardware.display.DisplayManagerGlobal;
+import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
+import android.net.LinkProperties;
+import android.net.Network;
import android.net.Proxy;
import android.net.ProxyInfo;
import android.net.Uri;
@@ -60,6 +63,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
+import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.Process;
@@ -83,6 +87,8 @@ import android.util.Slog;
import android.util.SuperNotCalledException;
import android.view.Display;
import android.view.HardwareRenderer;
+import android.view.IWindowManager;
+import android.view.IWindowSessionCallback;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewManager;
@@ -94,6 +100,7 @@ import android.renderscript.RenderScript;
import android.security.AndroidKeyStoreProvider;
import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SamplingProfilerIntegration;
@@ -117,7 +124,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;
-import java.util.regex.Pattern;
import libcore.io.DropBox;
import libcore.io.EventLogger;
@@ -156,11 +162,17 @@ public final class ActivityThread {
private static final boolean DEBUG_MEMORY_TRIM = false;
private static final boolean DEBUG_PROVIDER = false;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
- private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";");
private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;
private static final int LOG_ON_PAUSE_CALLED = 30021;
private static final int LOG_ON_RESUME_CALLED = 30022;
+ /** Type for IActivityManager.serviceDoneExecuting: anonymous operation */
+ public static final int SERVICE_DONE_EXECUTING_ANON = 0;
+ /** Type for IActivityManager.serviceDoneExecuting: done with an onStart call */
+ public static final int SERVICE_DONE_EXECUTING_START = 1;
+ /** Type for IActivityManager.serviceDoneExecuting: done stopping (destroying) service */
+ public static final int SERVICE_DONE_EXECUTING_STOP = 2;
+
private ContextImpl mSystemContext;
static IPackageManager sPackageManager;
@@ -268,6 +280,7 @@ public final class ActivityThread {
IBinder token;
int ident;
Intent intent;
+ String referrer;
IVoiceInteractor voiceInteractor;
Bundle state;
PersistableBundle persistentState;
@@ -290,7 +303,7 @@ public final class ActivityThread {
LoadedApk packageInfo;
List<ResultInfo> pendingResults;
- List<Intent> pendingIntents;
+ List<ReferrerIntent> pendingIntents;
boolean startsNotResumed;
boolean isForward;
@@ -348,7 +361,7 @@ public final class ActivityThread {
}
static final class NewIntentData {
- List<Intent> intents;
+ List<ReferrerIntent> intents;
IBinder token;
public String toString() {
return "NewIntentData{intents=" + intents + " token=" + token + "}";
@@ -605,9 +618,9 @@ 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,
+ String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+ List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
@@ -617,6 +630,7 @@ public final class ActivityThread {
r.token = token;
r.ident = ident;
r.intent = intent;
+ r.referrer = referrer;
r.voiceInteractor = voiceInteractor;
r.activityInfo = info;
r.compatInfo = compatInfo;
@@ -637,13 +651,13 @@ public final class ActivityThread {
}
public final void scheduleRelaunchActivity(IBinder token,
- List<ResultInfo> pendingResults, List<Intent> pendingNewIntents,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
int configChanges, boolean notResumed, Configuration config) {
requestRelaunchActivity(token, pendingResults, pendingNewIntents,
configChanges, notResumed, config, true);
}
- public final void scheduleNewIntent(List<Intent> intents, IBinder token) {
+ public final void scheduleNewIntent(List<ReferrerIntent> intents, IBinder token) {
NewIntentData data = new NewIntentData();
data.intents = intents;
data.token = token;
@@ -833,7 +847,13 @@ public final class ActivityThread {
}
public void setHttpProxy(String host, String port, String exclList, Uri pacFileUrl) {
- Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
+ final Network network = ConnectivityManager.getProcessDefaultNetwork();
+ if (network != null) {
+ Proxy.setHttpProxySystemProperty(
+ ConnectivityManager.from(getSystemContext()).getDefaultProxy());
+ } else {
+ Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
+ }
}
public void processInBackground() {
@@ -962,6 +982,8 @@ public final class ActivityThread {
int binderLocalObjectCount = Debug.getBinderLocalObjectCount();
int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
+ long parcelSize = Parcel.getGlobalAllocSize();
+ long parcelCount = Parcel.getGlobalAllocCount();
long openSslSocketCount = Debug.countInstancesOfClass(OpenSSLSocketImpl.class);
SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();
@@ -1020,9 +1042,10 @@ public final class ActivityThread {
printRow(pw, TWO_COUNT_COLUMNS, "Local Binders:", binderLocalObjectCount,
"Proxy Binders:", binderProxyObjectCount);
- printRow(pw, ONE_COUNT_COLUMN, "Death Recipients:", binderDeathObjectCount);
-
- printRow(pw, ONE_COUNT_COLUMN, "OpenSSL Sockets:", openSslSocketCount);
+ printRow(pw, TWO_COUNT_COLUMNS, "Parcel memory:", parcelSize/1024,
+ "Parcel count:", parcelCount);
+ printRow(pw, TWO_COUNT_COLUMNS, "Death Recipients:", binderDeathObjectCount,
+ "OpenSSL Sockets:", openSslSocketCount);
// SQLite mem info
pw.println(" ");
@@ -1765,6 +1788,12 @@ public final class ActivityThread {
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
+
+ if (mSystemThread && "android".equals(aInfo.packageName)) {
+ packageInfo.installSystemApplicationInfo(aInfo,
+ getSystemContext().mPackageInfo.getClassLoader());
+ }
+
if (includeCode) {
mPackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
@@ -1825,10 +1854,6 @@ public final class ActivityThread {
synchronized (this) {
getSystemContext().installSystemApplicationInfo(info, classLoader);
- // The code package for "android" in the system server needs
- // to be the system context's package.
- mPackages.put("android", new WeakReference<LoadedApk>(getSystemContext().mPackageInfo));
-
// give ourselves a default profiler
mProfiler = new Profiler();
}
@@ -2255,7 +2280,7 @@ public final class ActivityThread {
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
- r.voiceInteractor);
+ r.referrer, r.voiceInteractor);
if (customIntent != null) {
activity.mIntent = customIntent;
@@ -2380,6 +2405,9 @@ public final class ActivityThread {
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
+ // Initialize before creating the activity
+ WindowManagerGlobal.initialize();
+
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
@@ -2442,11 +2470,10 @@ public final class ActivityThread {
}
}
- private void deliverNewIntents(ActivityClientRecord r,
- List<Intent> intents) {
+ private void deliverNewIntents(ActivityClientRecord r, List<ReferrerIntent> intents) {
final int N = intents.size();
for (int i=0; i<N; i++) {
- Intent intent = intents.get(i);
+ ReferrerIntent intent = intents.get(i);
intent.setExtrasClassLoader(r.activity.getClassLoader());
intent.prepareToEnterProcess();
r.activity.mFragments.noteStateNotSaved();
@@ -2454,8 +2481,7 @@ public final class ActivityThread {
}
}
- public final void performNewIntents(IBinder token,
- List<Intent> intents) {
+ public final void performNewIntents(IBinder token, List<ReferrerIntent> intents) {
ActivityClientRecord r = mActivities.get(token);
if (r != null) {
final boolean resumed = !r.paused;
@@ -2536,13 +2562,18 @@ public final class ActivityThread {
}
public void handleInstallProvider(ProviderInfo info) {
- installContentProviders(mInitialApplication, Lists.newArrayList(info));
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+ try {
+ installContentProviders(mInitialApplication, Lists.newArrayList(info));
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
}
private void handleEnterAnimationComplete(IBinder token) {
ActivityClientRecord r = mActivities.get(token);
if (r != null) {
- r.activity.onEnterAnimationComplete();
+ r.activity.dispatchEnterAnimationComplete();
}
}
@@ -2755,7 +2786,7 @@ public final class ActivityThread {
mServices.put(data.token, service);
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
- data.token, 0, 0, 0);
+ data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (RemoteException e) {
// nothing to do.
}
@@ -2784,7 +2815,7 @@ public final class ActivityThread {
} else {
s.onRebind(data.intent);
ActivityManagerNative.getDefault().serviceDoneExecuting(
- data.token, 0, 0, 0);
+ data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
}
ensureJitEnabled();
} catch (RemoteException ex) {
@@ -2812,7 +2843,7 @@ public final class ActivityThread {
data.token, data.intent, doRebind);
} else {
ActivityManagerNative.getDefault().serviceDoneExecuting(
- data.token, 0, 0, 0);
+ data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
}
} catch (RemoteException ex) {
}
@@ -2894,7 +2925,7 @@ public final class ActivityThread {
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
- data.token, 1, data.startId, res);
+ data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);
} catch (RemoteException e) {
// nothing to do.
}
@@ -2925,9 +2956,11 @@ public final class ActivityThread {
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
- token, 0, 0, 0);
+ token, SERVICE_DONE_EXECUTING_STOP, 0, 0);
} catch (RemoteException e) {
// nothing to do.
+ Slog.i(TAG, "handleStopService: unable to execute serviceDoneExecuting for "
+ + token, e);
}
} catch (Exception e) {
if (!mInstrumentation.onException(s, e)) {
@@ -2935,7 +2968,10 @@ public final class ActivityThread {
"Unable to stop service " + s
+ ": " + e.toString(), e);
}
+ Slog.i(TAG, "handleStopService: exception for " + token, e);
}
+ } else {
+ Slog.i(TAG, "handleStopService: token=" + token + " not found.");
}
//Slog.i(TAG, "Running services: " + mServices);
}
@@ -3768,7 +3804,7 @@ public final class ActivityThread {
}
public final void requestRelaunchActivity(IBinder token,
- List<ResultInfo> pendingResults, List<Intent> pendingNewIntents,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
int configChanges, boolean notResumed, Configuration config,
boolean fromServer) {
ActivityClientRecord target = null;
@@ -4444,7 +4480,7 @@ public final class ActivityThread {
// crash if we can't get it.
IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
try {
- ProxyInfo proxyInfo = service.getProxy();
+ final ProxyInfo proxyInfo = service.getDefaultProxy();
Proxy.setHttpProxySystemProperty(proxyInfo);
} catch (RemoteException e) {}
}
@@ -4918,7 +4954,7 @@ public final class ActivityThread {
private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
ContentProvider localProvider, IActivityManager.ContentProviderHolder holder) {
- final String auths[] = PATTERN_SEMICOLON.split(holder.info.authority);
+ final String auths[] = holder.info.authority.split(";");
final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);
final ProviderClientRecord pcr = new ProviderClientRecord(
@@ -5246,8 +5282,6 @@ public final class ActivityThread {
sMainThreadHandler = thread.getHandler();
}
- AsyncTask.init();
-
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index 540376e..e3b27b5 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -206,6 +206,8 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
private ArrayList<GhostViewListeners> mGhostViewListeners =
new ArrayList<GhostViewListeners>();
private ArrayMap<View, Float> mOriginalAlphas = new ArrayMap<View, Float>();
+ final private ArrayList<View> mRootSharedElements = new ArrayList<View>();
+ private ArrayList<Matrix> mSharedElementParentMatrices;
public ActivityTransitionCoordinator(Window window,
ArrayList<String> allSharedElementNames,
@@ -222,8 +224,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
if (mListener != null) {
mListener.onMapSharedElements(mAllSharedElementNames, sharedElements);
}
- mSharedElementNames.addAll(sharedElements.keySet());
- mSharedElements.addAll(sharedElements.values());
+ setSharedElements(sharedElements);
if (getViewsTransition() != null && mTransitioningViews != null) {
ViewGroup decorView = getDecor();
if (decorView != null) {
@@ -234,6 +235,58 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
setEpicenter();
}
+ /**
+ * Iterates over the shared elements and adds them to the members in order.
+ * Shared elements that are nested in other shared elements are placed after the
+ * elements that they are nested in. This means that layout ordering can be done
+ * from first to last.
+ *
+ * @param sharedElements The map of transition names to shared elements to set into
+ * the member fields.
+ */
+ private void setSharedElements(ArrayMap<String, View> sharedElements) {
+ boolean isFirstRun = true;
+ while (!sharedElements.isEmpty()) {
+ final int numSharedElements = sharedElements.size();
+ for (int i = numSharedElements - 1; i >= 0; i--) {
+ final View view = sharedElements.valueAt(i);
+ final String name = sharedElements.keyAt(i);
+ if (isFirstRun && (view == null || !view.isAttachedToWindow() || name == null)) {
+ sharedElements.removeAt(i);
+ } else {
+ if (!isNested(view, sharedElements)) {
+ mSharedElementNames.add(name);
+ mSharedElements.add(view);
+ sharedElements.removeAt(i);
+ if (isFirstRun) {
+ // We need to keep track which shared elements are roots
+ // and which are nested.
+ mRootSharedElements.add(view);
+ }
+ }
+ }
+ }
+ isFirstRun = false;
+ }
+ }
+
+ /**
+ * Returns true when view is nested in any of the values of sharedElements.
+ */
+ private static boolean isNested(View view, ArrayMap<String, View> sharedElements) {
+ ViewParent parent = view.getParent();
+ boolean isNested = false;
+ while (parent instanceof View) {
+ View parentView = (View) parent;
+ if (sharedElements.containsValue(parentView)) {
+ isNested = true;
+ break;
+ }
+ parent = parentView.getParent();
+ }
+ return isNested;
+ }
+
protected void stripOffscreenViews() {
if (mTransitioningViews == null) {
return;
@@ -449,11 +502,50 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
view.layout(x, y, x + width, y + height);
}
- protected void getSharedElementParentMatrix(View view, Matrix matrix) {
- // Find the location in the view's parent
- ViewGroup parent = (ViewGroup) view.getParent();
- matrix.reset();
- parent.transformMatrixToLocal(matrix);
+ private void setSharedElementMatrices() {
+ int numSharedElements = mSharedElements.size();
+ if (numSharedElements > 0) {
+ mSharedElementParentMatrices = new ArrayList<Matrix>(numSharedElements);
+ }
+ for (int i = 0; i < numSharedElements; i++) {
+ View view = mSharedElements.get(i);
+
+ // Find the location in the view's parent
+ ViewGroup parent = (ViewGroup) view.getParent();
+ Matrix matrix = new Matrix();
+ parent.transformMatrixToLocal(matrix);
+
+ mSharedElementParentMatrices.add(matrix);
+ }
+ }
+
+ private void getSharedElementParentMatrix(View view, Matrix matrix) {
+ final boolean isNestedInOtherSharedElement = !mRootSharedElements.contains(view);
+ final boolean useParentMatrix;
+ if (isNestedInOtherSharedElement) {
+ useParentMatrix = true;
+ } else {
+ final int index = mSharedElementParentMatrices == null ? -1
+ : mSharedElements.indexOf(view);
+ if (index < 0) {
+ useParentMatrix = true;
+ } else {
+ // The indices of mSharedElementParentMatrices matches the
+ // mSharedElement matrices.
+ Matrix parentMatrix = mSharedElementParentMatrices.get(index);
+ matrix.set(parentMatrix);
+ useParentMatrix = false;
+ }
+ }
+ if (useParentMatrix) {
+ matrix.reset();
+ ViewParent viewParent = view.getParent();
+ if (viewParent instanceof ViewGroup) {
+ // Find the location in the view's parent
+ ViewGroup parent = (ViewGroup) viewParent;
+ parent.transformMatrixToLocal(matrix);
+ }
+ }
}
protected ArrayList<SharedElementOriginalState> setSharedElementState(
@@ -536,29 +628,31 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) {
int numSharedElements = names.size();
+ ArrayList<View> snapshots = new ArrayList<View>(numSharedElements);
if (numSharedElements == 0) {
- return null;
+ return snapshots;
}
- ArrayList<View> snapshots = new ArrayList<View>(numSharedElements);
Context context = getWindow().getContext();
int[] decorLoc = new int[2];
ViewGroup decorView = getDecor();
if (decorView != null) {
decorView.getLocationOnScreen(decorLoc);
}
+ Matrix tempMatrix = new Matrix();
for (String name: names) {
Bundle sharedElementBundle = state.getBundle(name);
+ View snapshot = null;
if (sharedElementBundle != null) {
Parcelable parcelable = sharedElementBundle.getParcelable(KEY_SNAPSHOT);
- View snapshot = null;
if (parcelable != null && mListener != null) {
snapshot = mListener.onCreateSnapshotView(context, parcelable);
}
if (snapshot != null) {
- setSharedElementState(snapshot, name, state, null, null, decorLoc);
+ setSharedElementState(snapshot, name, state, tempMatrix, null, decorLoc);
}
- snapshots.add(snapshot);
}
+ // Even null snapshots are added so they remain in the same order as shared elements.
+ snapshots.add(snapshot);
}
return snapshots;
}
@@ -607,6 +701,8 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
mResultReceiver = null;
mPendingTransition = null;
mListener = null;
+ mRootSharedElements.clear();
+ mSharedElementParentMatrices = null;
}
protected long getFadeDuration() {
@@ -703,10 +799,20 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
mIsStartingTransition = false;
}
+ /**
+ * Cancels any pending transitions and returns true if there is a transition is in
+ * the middle of starting.
+ */
+ protected boolean cancelPendingTransitions() {
+ mPendingTransition = null;
+ return mIsStartingTransition;
+ }
+
protected void moveSharedElementsToOverlay() {
- if (!mWindow.getSharedElementsUseOverlay()) {
+ if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) {
return;
}
+ setSharedElementMatrices();
int numSharedElements = mSharedElements.size();
ViewGroup decor = getDecor();
if (decor != null) {
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 555d20b..a2bfa4e 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -22,6 +22,7 @@ import android.util.ArrayMap;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.Window;
import java.lang.ref.WeakReference;
@@ -252,7 +253,7 @@ class ActivityTransitionState {
}
}
- public boolean startExitBackTransition(Activity activity) {
+ public boolean startExitBackTransition(final Activity activity) {
if (mEnteringNames == null) {
return false;
} else {
@@ -260,10 +261,11 @@ class ActivityTransitionState {
mHasExited = true;
Transition enterViewsTransition = null;
ViewGroup decor = null;
+ boolean delayExitBack = false;
if (mEnterTransitionCoordinator != null) {
enterViewsTransition = mEnterTransitionCoordinator.getEnterViewsTransition();
decor = mEnterTransitionCoordinator.getDecor();
- mEnterTransitionCoordinator.cancelEnter();
+ delayExitBack = mEnterTransitionCoordinator.cancelEnter();
mEnterTransitionCoordinator = null;
if (enterViewsTransition != null && decor != null) {
enterViewsTransition.pause(decor);
@@ -275,7 +277,23 @@ class ActivityTransitionState {
if (enterViewsTransition != null && decor != null) {
enterViewsTransition.resume(decor);
}
- mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData);
+ if (delayExitBack && decor != null) {
+ final ViewGroup finalDecor = decor;
+ decor.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ finalDecor.getViewTreeObserver().removeOnPreDrawListener(this);
+ if (mReturnExitCoordinator != null) {
+ mReturnExitCoordinator.startExit(activity.mResultCode,
+ activity.mResultData);
+ }
+ return true;
+ }
+ });
+ } else {
+ mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData);
+ }
}
return true;
}
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index 4ce7835..3c6458f 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -466,6 +466,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Set the resource id of the {@link Drawable} to be used in the title.
+ * <p>
+ * Takes precedence over values set using {@link #setIcon(Drawable)}.
*
* @return This Builder object to allow for chaining of calls to set methods
*/
@@ -485,7 +487,11 @@ public class AlertDialog extends Dialog implements DialogInterface {
}
/**
- * Set an icon as supplied by a theme attribute. e.g. android.R.attr.alertDialogIcon
+ * Set an icon as supplied by a theme attribute. e.g.
+ * {@link android.R.attr#alertDialogIcon}.
+ * <p>
+ * Takes precedence over values set using {@link #setIcon(int)} or
+ * {@link #setIcon(Drawable)}.
*
* @param attrId ID of a theme attribute that points to a drawable resource.
*/
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index ba9c9d6..95870cf 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -26,6 +26,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
@@ -734,6 +735,18 @@ public class AppOpsManager {
}
/**
+ * @hide
+ */
+ public static int strDebugOpToOp(String op) {
+ for (int i=0; i<sOpNames.length; i++) {
+ if (sOpNames[i].equals(op)) {
+ return i;
+ }
+ }
+ throw new IllegalArgumentException("Unknown operation string: " + op);
+ }
+
+ /**
* Retrieve the permission associated with an operation, or null if there is not one.
* @hide
*/
@@ -996,7 +1009,7 @@ public class AppOpsManager {
/** @hide */
public void resetAllModes() {
try {
- mService.resetAllModes();
+ mService.resetAllModes(UserHandle.myUserId(), null);
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 14dccfb..d808c8b 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1615,6 +1615,18 @@ final class ApplicationPackageManager extends PackageManager {
return null;
}
+ /**
+ * @hide
+ */
+ @Override
+ public boolean isUpgrade() {
+ try {
+ return mPM.isUpgrade();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
@Override
public PackageInstaller getPackageInstaller() {
synchronized (mLock) {
@@ -1670,6 +1682,17 @@ final class ApplicationPackageManager extends PackageManager {
* @hide
*/
public Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) {
+ Drawable dr = loadUnbadgedItemIcon(itemInfo, appInfo);
+ if (itemInfo.showUserIcon != UserHandle.USER_NULL) {
+ return dr;
+ }
+ return getUserBadgedIcon(dr, new UserHandle(mContext.getUserId()));
+ }
+
+ /**
+ * @hide
+ */
+ public Drawable loadUnbadgedItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) {
if (itemInfo.showUserIcon != UserHandle.USER_NULL) {
Bitmap bitmap = getUserManager().getUserIcon(itemInfo.showUserIcon);
if (bitmap == null) {
@@ -1684,7 +1707,7 @@ final class ApplicationPackageManager extends PackageManager {
if (dr == null) {
dr = itemInfo.loadDefaultIcon(this);
}
- return getUserBadgedIcon(dr, new UserHandle(mContext.getUserId()));
+ return dr;
}
private Drawable getBadgedDrawable(Drawable drawable, Drawable badgeDrawable,
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index b2bfc13..eb3ddb2 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -36,6 +36,7 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -140,19 +141,21 @@ public abstract class ApplicationThreadNative extends Binder
ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
Configuration curConfig = Configuration.CREATOR.createFromParcel(data);
CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
+ String referrer = data.readString();
IVoiceInteractor voiceInteractor = IVoiceInteractor.Stub.asInterface(
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);
+ List<ReferrerIntent> pi = data.createTypedArrayList(ReferrerIntent.CREATOR);
boolean notResumed = data.readInt() != 0;
boolean isForward = data.readInt() != 0;
ProfilerInfo profilerInfo = data.readInt() != 0
? ProfilerInfo.CREATOR.createFromParcel(data) : null;
- scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, voiceInteractor,
- procState, state, persistentState, ri, pi, notResumed, isForward, profilerInfo);
+ scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, referrer,
+ voiceInteractor, procState, state, persistentState, ri, pi,
+ notResumed, isForward, profilerInfo);
return true;
}
@@ -161,7 +164,7 @@ public abstract class ApplicationThreadNative extends Binder
data.enforceInterface(IApplicationThread.descriptor);
IBinder b = data.readStrongBinder();
List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
- List<Intent> pi = data.createTypedArrayList(Intent.CREATOR);
+ List<ReferrerIntent> pi = data.createTypedArrayList(ReferrerIntent.CREATOR);
int configChanges = data.readInt();
boolean notResumed = data.readInt() != 0;
Configuration config = null;
@@ -175,7 +178,7 @@ public abstract class ApplicationThreadNative extends Binder
case SCHEDULE_NEW_INTENT_TRANSACTION:
{
data.enforceInterface(IApplicationThread.descriptor);
- List<Intent> pi = data.createTypedArrayList(Intent.CREATOR);
+ List<ReferrerIntent> pi = data.createTypedArrayList(ReferrerIntent.CREATOR);
IBinder b = data.readStrongBinder();
scheduleNewIntent(pi, b);
return true;
@@ -773,9 +776,9 @@ 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,
+ String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+ List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
ProfilerInfo profilerInfo) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
@@ -785,6 +788,7 @@ class ApplicationThreadProxy implements IApplicationThread {
info.writeToParcel(data, 0);
curConfig.writeToParcel(data, 0);
compatInfo.writeToParcel(data, 0);
+ data.writeString(referrer);
data.writeStrongBinder(voiceInteractor != null ? voiceInteractor.asBinder() : null);
data.writeInt(procState);
data.writeBundle(state);
@@ -805,7 +809,7 @@ class ApplicationThreadProxy implements IApplicationThread {
}
public final void scheduleRelaunchActivity(IBinder token,
- List<ResultInfo> pendingResults, List<Intent> pendingNewIntents,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
int configChanges, boolean notResumed, Configuration config)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -826,7 +830,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
- public void scheduleNewIntent(List<Intent> intents, IBinder token)
+ public void scheduleNewIntent(List<ReferrerIntent> intents, IBinder token)
throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index 0092ee7..2784d44 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -38,6 +38,7 @@ import android.view.ViewTreeObserver;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
final class BackStackState implements Parcelable {
final int[] mOps;
@@ -1055,7 +1056,7 @@ final class BackStackRecord extends FragmentTransaction implements
}
private static ArrayList<View> captureExitingViews(Transition exitTransition,
- Fragment outFragment, ArrayMap<String, View> namedViews) {
+ Fragment outFragment, ArrayMap<String, View> namedViews, View nonExistentView) {
ArrayList<View> viewList = null;
if (exitTransition != null) {
viewList = new ArrayList<View>();
@@ -1064,7 +1065,10 @@ final class BackStackRecord extends FragmentTransaction implements
if (namedViews != null) {
viewList.removeAll(namedViews.values());
}
- addTargets(exitTransition, viewList);
+ if (!viewList.isEmpty()) {
+ viewList.add(nonExistentView);
+ addTargets(exitTransition, viewList);
+ }
}
return viewList;
}
@@ -1132,11 +1136,8 @@ final class BackStackRecord extends FragmentTransaction implements
namedViews = mapSharedElementsIn(state, isBack, inFragment);
removeTargets(sharedElementTransition, sharedElementTargets);
sharedElementTargets.clear();
- if (namedViews.isEmpty()) {
- sharedElementTargets.add(state.nonExistentView);
- } else {
- sharedElementTargets.addAll(namedViews.values());
- }
+ sharedElementTargets.add(state.nonExistentView);
+ sharedElementTargets.addAll(namedViews.values());
addTargets(sharedElementTransition, sharedElementTargets);
@@ -1153,6 +1154,9 @@ final class BackStackRecord extends FragmentTransaction implements
if (namedViews != null) {
enteringViews.removeAll(namedViews.values());
}
+ enteringViews.add(state.nonExistentView);
+ // We added this earlier to prevent any views being targeted.
+ enterTransition.removeTarget(state.nonExistentView);
addTargets(enterTransition, enteringViews);
}
setSharedElementEpicenter(enterTransition, state);
@@ -1293,11 +1297,8 @@ final class BackStackRecord extends FragmentTransaction implements
ArrayList<View> sharedElementTargets = new ArrayList<View>();
if (sharedElementTransition != null) {
namedViews = remapSharedElements(state, outFragment, isBack);
- if (namedViews.isEmpty()) {
- sharedElementTargets.add(state.nonExistentView);
- } else {
- sharedElementTargets.addAll(namedViews.values());
- }
+ sharedElementTargets.add(state.nonExistentView);
+ sharedElementTargets.addAll(namedViews.values());
addTargets(sharedElementTransition, sharedElementTargets);
// Notify the start of the transition.
@@ -1310,7 +1311,7 @@ final class BackStackRecord extends FragmentTransaction implements
}
ArrayList<View> exitingViews = captureExitingViews(exitTransition, outFragment,
- namedViews);
+ namedViews, state.nonExistentView);
if (exitingViews == null || exitingViews.isEmpty()) {
exitTransition = null;
}
@@ -1388,20 +1389,69 @@ final class BackStackRecord extends FragmentTransaction implements
}
}
- private static void removeTargets(Transition transition, ArrayList<View> views) {
- int numViews = views.size();
- for (int i = 0; i < numViews; i++) {
- transition.removeTarget(views.get(i));
+ /**
+ * This method removes the views from transitions that target ONLY those views.
+ * The views list should match those added in addTargets and should contain
+ * one view that is not in the view hierarchy (state.nonExistentView).
+ */
+ public static void removeTargets(Transition transition, ArrayList<View> views) {
+ if (transition instanceof TransitionSet) {
+ TransitionSet set = (TransitionSet) transition;
+ int numTransitions = set.getTransitionCount();
+ for (int i = 0; i < numTransitions; i++) {
+ Transition child = set.getTransitionAt(i);
+ removeTargets(child, views);
+ }
+ } else if (!hasSimpleTarget(transition)) {
+ List<View> targets = transition.getTargets();
+ if (targets != null && targets.size() == views.size() &&
+ targets.containsAll(views)) {
+ // We have an exact match. We must have added these earlier in addTargets
+ for (int i = views.size() - 1; i >= 0; i--) {
+ transition.removeTarget(views.get(i));
+ }
+ }
}
}
- private static void addTargets(Transition transition, ArrayList<View> views) {
- int numViews = views.size();
- for (int i = 0; i < numViews; i++) {
- transition.addTarget(views.get(i));
+ /**
+ * This method adds views as targets to the transition, but only if the transition
+ * doesn't already have a target. It is best for views to contain one View object
+ * that does not exist in the view hierarchy (state.nonExistentView) so that
+ * when they are removed later, a list match will suffice to remove the targets.
+ * Otherwise, if you happened to have targeted the exact views for the transition,
+ * the removeTargets call will remove them unexpectedly.
+ */
+ public static void addTargets(Transition transition, ArrayList<View> views) {
+ if (transition instanceof TransitionSet) {
+ TransitionSet set = (TransitionSet) transition;
+ int numTransitions = set.getTransitionCount();
+ for (int i = 0; i < numTransitions; i++) {
+ Transition child = set.getTransitionAt(i);
+ addTargets(child, views);
+ }
+ } else if (!hasSimpleTarget(transition)) {
+ List<View> targets = transition.getTargets();
+ if (isNullOrEmpty(targets)) {
+ // We can just add the target views
+ int numViews = views.size();
+ for (int i = 0; i < numViews; i++) {
+ transition.addTarget(views.get(i));
+ }
+ }
}
}
+ private static boolean hasSimpleTarget(Transition transition) {
+ return !isNullOrEmpty(transition.getTargetIds()) ||
+ !isNullOrEmpty(transition.getTargetNames()) ||
+ !isNullOrEmpty(transition.getTargetTypes());
+ }
+
+ private static boolean isNullOrEmpty(List list) {
+ return list == null || list.isEmpty();
+ }
+
/**
* Remaps a name-to-View map, substituting different names for keys.
*
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 7fafc38..2ef046d 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -20,9 +20,9 @@ import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageStatsManager;
import android.appwidget.AppWidgetManager;
import android.os.Build;
-
import android.service.persistentdata.IPersistentDataBlockService;
import android.service.persistentdata.PersistentDataBlockManager;
+
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.policy.PolicyManager;
import com.android.internal.util.Preconditions;
@@ -125,6 +125,7 @@ import android.print.PrintManager;
import android.service.fingerprint.IFingerprintService;
import android.service.fingerprint.FingerprintManager;
import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.content.ClipboardManager;
import android.util.AndroidRuntimeException;
@@ -561,6 +562,11 @@ class ContextImpl extends Context {
return new TelephonyManager(ctx.getOuterContext());
}});
+ registerService(TELEPHONY_SUBSCRIPTION_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ return new SubscriptionManager(ctx.getOuterContext());
+ }});
+
registerService(TELECOM_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return new TelecomManager(ctx.getOuterContext());
@@ -1863,6 +1869,21 @@ class ContextImpl extends Context {
}
}
+ /** @hide */
+ @Override
+ public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
+ if (permission == null) {
+ throw new IllegalArgumentException("permission is null");
+ }
+
+ try {
+ return ActivityManagerNative.getDefault().checkPermissionWithToken(
+ permission, pid, uid, callerToken);
+ } catch (RemoteException e) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ }
+
@Override
public int checkCallingPermission(String permission) {
if (permission == null) {
@@ -1951,7 +1972,19 @@ class ContextImpl extends Context {
try {
return ActivityManagerNative.getDefault().checkUriPermission(
ContentProvider.getUriWithoutUserId(uri), pid, uid, modeFlags,
- resolveUserId(uri));
+ resolveUserId(uri), null);
+ } catch (RemoteException e) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ }
+
+ /** @hide */
+ @Override
+ public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) {
+ try {
+ return ActivityManagerNative.getDefault().checkUriPermission(
+ ContentProvider.getUriWithoutUserId(uri), pid, uid, modeFlags,
+ resolveUserId(uri), callerToken);
} catch (RemoteException e) {
return PackageManager.PERMISSION_DENIED;
}
@@ -2386,7 +2419,7 @@ class ContextImpl extends Context {
int res = -1;
try {
res = mount.mkdirs(getPackageName(), dir.getAbsolutePath());
- } catch (RemoteException e) {
+ } catch (Exception ignored) {
}
if (res != 0) {
Log.w(TAG, "Failed to ensure directory: " + dir);
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 12d4513..067073a 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -910,21 +910,27 @@ public class Dialog implements DialogInterface, Window.Callback,
* @see Activity#openOptionsMenu()
*/
public void openOptionsMenu() {
- mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+ if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL)) {
+ mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+ }
}
-
+
/**
* @see Activity#closeOptionsMenu()
*/
public void closeOptionsMenu() {
- mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
+ if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL)) {
+ mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
+ }
}
/**
* @see Activity#invalidateOptionsMenu()
*/
public void invalidateOptionsMenu() {
- mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+ if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL)) {
+ mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+ }
}
/**
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index add67f2..c053c83 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -18,7 +18,6 @@ package android.app;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
-import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.ResultReceiver;
@@ -57,7 +56,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
private boolean mIsViewsTransitionStarted;
private boolean mIsViewsTransitionComplete;
private boolean mIsSharedElementTransitionComplete;
- private ArrayList<Matrix> mSharedElementParentMatrices;
private Transition mEnterViewsTransition;
public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
@@ -122,7 +120,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
if (mIsReturning) {
sendSharedElementDestination();
} else {
- setSharedElementMatrices();
moveSharedElementsToOverlay();
}
if (mSharedElementsBundle != null) {
@@ -135,16 +132,17 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
return;
}
mAreViewsReady = true;
+ final ViewGroup decor = getDecor();
// Ensure the views have been laid out before capturing the views -- we need the epicenter.
- if (sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()) {
+ if (decor == null || (decor.isAttachedToWindow() &&
+ (sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()))) {
viewsReady(sharedElements);
} else {
- final View sharedElement = sharedElements.valueAt(0);
- sharedElement.getViewTreeObserver()
+ decor.getViewTreeObserver()
.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
- sharedElement.getViewTreeObserver().removeOnPreDrawListener(this);
+ decor.getViewTreeObserver().removeOnPreDrawListener(this);
viewsReady(sharedElements);
return true;
}
@@ -194,7 +192,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
}
if (allReady) {
Bundle state = captureSharedElementState();
- setSharedElementMatrices();
moveSharedElementsToOverlay();
mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
} else if (decorView != null) {
@@ -205,7 +202,6 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
decorView.getViewTreeObserver().removeOnPreDrawListener(this);
if (mResultReceiver != null) {
Bundle state = captureSharedElementState();
- setSharedElementMatrices();
moveSharedElementsToOverlay();
mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
}
@@ -322,6 +318,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
if (mListener != null) {
mListener.onRejectSharedElements(rejectedSnapshots);
}
+ removeNullViews(rejectedSnapshots);
startRejectedAnimations(rejectedSnapshots);
// Now start shared element transition
@@ -370,6 +367,16 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
}
}
+ private static void removeNullViews(ArrayList<View> views) {
+ if (views != null) {
+ for (int i = views.size() - 1; i >= 0; i--) {
+ if (views.get(i) == null) {
+ views.remove(i);
+ }
+ }
+ }
+ }
+
private void onTakeSharedElements() {
if (!mIsReadyForTransition || mSharedElementsBundle == null) {
return;
@@ -557,7 +564,12 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
clearState();
}
- public void cancelEnter() {
+ /**
+ * Cancels the enter transition.
+ * @return True if the enter transition is still pending capturing the target state. If so,
+ * any transition started on the decor will do nothing.
+ */
+ public boolean cancelEnter() {
setGhostVisibility(View.INVISIBLE);
mHasStopped = true;
mIsCanceled = true;
@@ -568,6 +580,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
}
mActivity = null;
clearState();
+ return super.cancelPendingTransitions();
}
private void makeOpaque() {
@@ -634,30 +647,4 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
});
}
- private void setSharedElementMatrices() {
- int numSharedElements = mSharedElements.size();
- if (numSharedElements > 0) {
- mSharedElementParentMatrices = new ArrayList<Matrix>(numSharedElements);
- }
- for (int i = 0; i < numSharedElements; i++) {
- View view = mSharedElements.get(i);
-
- // Find the location in the view's parent
- ViewGroup parent = (ViewGroup) view.getParent();
- Matrix matrix = new Matrix();
- parent.transformMatrixToLocal(matrix);
-
- mSharedElementParentMatrices.add(matrix);
- }
- }
-
- @Override
- protected void getSharedElementParentMatrix(View view, Matrix matrix) {
- int index = mSharedElementParentMatrices == null ? -1 : mSharedElements.indexOf(view);
- if (index < 0) {
- super.getSharedElementParentMatrix(view, matrix);
- } else {
- matrix.set(mSharedElementParentMatrices.get(index));
- }
- }
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index de47147..70c14c6 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -140,6 +140,7 @@ public interface IActivityManager extends IInterface {
public StackInfo getStackInfo(int stackId) throws RemoteException;
public boolean isInHomeStack(int taskId) throws RemoteException;
public void setFocusedStack(int stackId) throws RemoteException;
+ public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException;
public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException;
public ContentProviderHolder getContentProvider(IApplicationThread caller,
String name, int userId, boolean stable) throws RemoteException;
@@ -220,9 +221,11 @@ 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, int userId)
+ public int checkPermissionWithToken(String permission, int pid, int uid, IBinder callerToken)
throws RemoteException;
+
+ public int checkUriPermission(Uri uri, int pid, int uid, int mode, int userId,
+ IBinder callerToken) 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)
@@ -374,7 +377,7 @@ public interface IActivityManager extends IInterface {
public boolean isUserRunning(int userid, boolean orStopping) throws RemoteException;
public int[] getRunningUserIds() throws RemoteException;
- public boolean removeTask(int taskId, int flags) throws RemoteException;
+ public boolean removeTask(int taskId) throws RemoteException;
public void registerProcessObserver(IProcessObserver observer) throws RemoteException;
public void unregisterProcessObserver(IProcessObserver observer) throws RemoteException;
@@ -457,6 +460,9 @@ public interface IActivityManager extends IInterface {
throws RemoteException;
public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException;
+ public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts)
+ throws RemoteException;
+
public boolean requestVisibleBehind(IBinder token, boolean visible) throws RemoteException;
public boolean isBackgroundVisibleBehind(IBinder token) throws RemoteException;
public void backgroundResourcesReleased(IBinder token) throws RemoteException;
@@ -464,6 +470,7 @@ public interface IActivityManager extends IInterface {
public void notifyLaunchTaskBehindComplete(IBinder token) throws RemoteException;
public void notifyEnterAnimationComplete(IBinder token) throws RemoteException;
+ public void systemBackupRestored() throws RemoteException;
public void notifyCleartextNetwork(int uid, byte[] firstPacket) throws RemoteException;
/*
@@ -784,6 +791,10 @@ public interface IActivityManager extends IInterface {
int BOOT_ANIMATION_COMPLETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+237;
int GET_TASK_DESCRIPTION_ICON_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+238;
int LAUNCH_ASSIST_INTENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+239;
+ int START_IN_PLACE_ANIMATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+240;
+ int CHECK_PERMISSION_WITH_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+241;
+ int REGISTER_TASK_STACK_LISTENER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+242;
+ int SYSTEM_BACKUP_RESTORED = IBinder.FIRST_CALL_TRANSACTION+243;
// Start of M transactions
int NOTIFY_CLEARTEXT_NETWORK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+280;
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 7ff207f..8bf8cd7 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -35,6 +35,7 @@ import android.os.IBinder;
import android.os.IInterface;
import android.service.voice.IVoiceInteractionSession;
import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
import java.io.FileDescriptor;
import java.util.List;
@@ -59,14 +60,14 @@ public interface IApplicationThread extends IInterface {
void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException;
void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
- IVoiceInteractor voiceInteractor, int procState, Bundle state,
+ String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+ List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
ProfilerInfo profilerInfo) throws RemoteException;
void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, int configChanges,
+ List<ReferrerIntent> pendingNewIntents, int configChanges,
boolean notResumed, Configuration config) throws RemoteException;
- void scheduleNewIntent(List<Intent> intent, IBinder token) throws RemoteException;
+ void scheduleNewIntent(List<ReferrerIntent> intent, IBinder token) throws RemoteException;
void scheduleDestroyActivity(IBinder token, boolean finished,
int configChanges) throws RemoteException;
void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo,
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index bdcff38..88b9080 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -71,6 +71,7 @@ interface INotificationManager
ComponentName getEffectsSuppressor();
boolean matchesCallFilter(in Bundle extras);
+ boolean isSystemConditionProviderEnabled(String path);
ZenModeConfig getZenModeConfig();
boolean setZenModeConfig(in ZenModeConfig config);
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
new file mode 100644
index 0000000..4b0935c
--- /dev/null
+++ b/core/java/android/app/ITaskStackListener.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 android.app;
+
+/** @hide */
+oneway interface ITaskStackListener {
+ void onTaskStackChanged();
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 60a013e..ad2b61f 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -45,6 +45,7 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.Window;
+import com.android.internal.content.ReferrerIntent;
import java.io.File;
import java.util.ArrayList;
@@ -1042,7 +1043,7 @@ public class Instrumentation {
activity.attach(context, aThread, this, token, 0, application, intent,
info, title, parent, id,
(Activity.NonConfigurationInstances)lastNonConfigurationInstance,
- new Configuration(), null);
+ new Configuration(), null, null);
return activity;
}
@@ -1211,6 +1212,21 @@ public class Instrumentation {
}
/**
+ * @hide
+ */
+ public void callActivityOnNewIntent(Activity activity, ReferrerIntent intent) {
+ final String oldReferrer = activity.mReferrer;
+ try {
+ if (intent != null) {
+ activity.mReferrer = intent.mReferrer;
+ }
+ callActivityOnNewIntent(activity, intent != null ? new Intent(intent) : null);
+ } finally {
+ activity.mReferrer = oldReferrer;
+ }
+ }
+
+ /**
* Perform calling of an activity's {@link Activity#onStart}
* method. The default implementation simply calls through to that method.
*
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index cc9aed8..ddd21e6 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -16,10 +16,14 @@
package android.app;
+import android.app.trust.ITrustManager;
+import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.RemoteException;
import android.os.IBinder;
+import android.os.ServiceManager;
+import android.os.UserHandle;
import android.view.IWindowManager;
import android.view.IOnKeyguardExitResult;
import android.view.WindowManagerGlobal;
@@ -33,6 +37,7 @@ import android.view.WindowManagerGlobal;
*/
public class KeyguardManager {
private IWindowManager mWM;
+ private ITrustManager mTrustManager;
/**
* Intent used to prompt user for device credentials.
@@ -151,6 +156,8 @@ public class KeyguardManager {
KeyguardManager() {
mWM = WindowManagerGlobal.getWindowManagerService();
+ mTrustManager = ITrustManager.Stub.asInterface(
+ ServiceManager.getService(Context.TRUST_SERVICE));
}
/**
@@ -218,6 +225,34 @@ public class KeyguardManager {
}
/**
+ * Returns whether the device is currently locked and requires a PIN, pattern or
+ * password to unlock.
+ *
+ * @return true if unlocking the device currently requires a PIN, pattern or
+ * password.
+ */
+ public boolean isDeviceLocked() {
+ return isDeviceLocked(UserHandle.getCallingUserId());
+ }
+
+ /**
+ * Returns whether the device is currently locked and requires a PIN, pattern or
+ * password to unlock.
+ *
+ * @param userId the user for which the locked state should be reported.
+ * @return true if unlocking the device currently requires a PIN, pattern or
+ * password.
+ * @hide
+ */
+ public boolean isDeviceLocked(int userId) {
+ try {
+ return mTrustManager.isDeviceLocked(userId);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
* @deprecated Use {@link android.view.WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD}
* and/or {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED}
* instead; this allows you to seamlessly hide the keyguard as your application
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 52db295..83c6c2b 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -638,55 +638,60 @@ public final class LoadedApk {
public void removeContextRegistrations(Context context,
String who, String what) {
final boolean reportRegistrationLeaks = StrictMode.vmRegistrationLeaksEnabled();
- ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> rmap =
- mReceivers.remove(context);
- if (rmap != null) {
- for (int i=0; i<rmap.size(); i++) {
- LoadedApk.ReceiverDispatcher rd = rmap.valueAt(i);
- IntentReceiverLeaked leak = new IntentReceiverLeaked(
- what + " " + who + " has leaked IntentReceiver "
- + rd.getIntentReceiver() + " that was " +
- "originally registered here. Are you missing a " +
- "call to unregisterReceiver()?");
- leak.setStackTrace(rd.getLocation().getStackTrace());
- Slog.e(ActivityThread.TAG, leak.getMessage(), leak);
- if (reportRegistrationLeaks) {
- StrictMode.onIntentReceiverLeaked(leak);
- }
- try {
- ActivityManagerNative.getDefault().unregisterReceiver(
- rd.getIIntentReceiver());
- } catch (RemoteException e) {
- // system crashed, nothing we can do
+ synchronized (mReceivers) {
+ ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> rmap =
+ mReceivers.remove(context);
+ if (rmap != null) {
+ for (int i = 0; i < rmap.size(); i++) {
+ LoadedApk.ReceiverDispatcher rd = rmap.valueAt(i);
+ IntentReceiverLeaked leak = new IntentReceiverLeaked(
+ what + " " + who + " has leaked IntentReceiver "
+ + rd.getIntentReceiver() + " that was " +
+ "originally registered here. Are you missing a " +
+ "call to unregisterReceiver()?");
+ leak.setStackTrace(rd.getLocation().getStackTrace());
+ Slog.e(ActivityThread.TAG, leak.getMessage(), leak);
+ if (reportRegistrationLeaks) {
+ StrictMode.onIntentReceiverLeaked(leak);
+ }
+ try {
+ ActivityManagerNative.getDefault().unregisterReceiver(
+ rd.getIIntentReceiver());
+ } catch (RemoteException e) {
+ // system crashed, nothing we can do
+ }
}
}
+ mUnregisteredReceivers.remove(context);
}
- mUnregisteredReceivers.remove(context);
- //Slog.i(TAG, "Receiver registrations: " + mReceivers);
- ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> smap =
- mServices.remove(context);
- if (smap != null) {
- for (int i=0; i<smap.size(); i++) {
- LoadedApk.ServiceDispatcher sd = smap.valueAt(i);
- ServiceConnectionLeaked leak = new ServiceConnectionLeaked(
- what + " " + who + " has leaked ServiceConnection "
- + sd.getServiceConnection() + " that was originally bound here");
- leak.setStackTrace(sd.getLocation().getStackTrace());
- Slog.e(ActivityThread.TAG, leak.getMessage(), leak);
- if (reportRegistrationLeaks) {
- StrictMode.onServiceConnectionLeaked(leak);
- }
- try {
- ActivityManagerNative.getDefault().unbindService(
- sd.getIServiceConnection());
- } catch (RemoteException e) {
- // system crashed, nothing we can do
+
+ synchronized (mServices) {
+ //Slog.i(TAG, "Receiver registrations: " + mReceivers);
+ ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> smap =
+ mServices.remove(context);
+ if (smap != null) {
+ for (int i = 0; i < smap.size(); i++) {
+ LoadedApk.ServiceDispatcher sd = smap.valueAt(i);
+ ServiceConnectionLeaked leak = new ServiceConnectionLeaked(
+ what + " " + who + " has leaked ServiceConnection "
+ + sd.getServiceConnection() + " that was originally bound here");
+ leak.setStackTrace(sd.getLocation().getStackTrace());
+ Slog.e(ActivityThread.TAG, leak.getMessage(), leak);
+ if (reportRegistrationLeaks) {
+ StrictMode.onServiceConnectionLeaked(leak);
+ }
+ try {
+ ActivityManagerNative.getDefault().unbindService(
+ sd.getIServiceConnection());
+ } catch (RemoteException e) {
+ // system crashed, nothing we can do
+ }
+ sd.doForget();
}
- sd.doForget();
}
+ mUnboundServices.remove(context);
+ //Slog.i(TAG, "Service registrations: " + mServices);
}
- mUnboundServices.remove(context);
- //Slog.i(TAG, "Service registrations: " + mServices);
}
public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index b654a6a..873e337 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -22,6 +22,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;
+import com.android.internal.content.ReferrerIntent;
import java.util.ArrayList;
import java.util.HashMap;
@@ -310,8 +311,8 @@ public class LocalActivityManager {
if (aInfo.launchMode != ActivityInfo.LAUNCH_MULTIPLE ||
(intent.getFlags()&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0) {
// The activity wants onNewIntent() called.
- ArrayList<Intent> intents = new ArrayList<Intent>(1);
- intents.add(intent);
+ ArrayList<ReferrerIntent> intents = new ArrayList<>(1);
+ intents.add(new ReferrerIntent(intent, mParent.getPackageName()));
if (localLOGV) Log.v(TAG, r.id + ": new intent");
mActivityThread.performNewIntents(r, intents);
r.intent = intent;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a363503..860b9cc 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
@@ -1112,7 +1113,11 @@ public class Notification implements Parcelable
/** Notification action extra which contains wearable extensions */
private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
+ // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options.
private static final String KEY_FLAGS = "flags";
+ private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel";
+ private static final String KEY_CONFIRM_LABEL = "confirmLabel";
+ private static final String KEY_CANCEL_LABEL = "cancelLabel";
// Flags bitwise-ored to mFlags
private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
@@ -1122,6 +1127,10 @@ public class Notification implements Parcelable
private int mFlags = DEFAULT_FLAGS;
+ private CharSequence mInProgressLabel;
+ private CharSequence mConfirmLabel;
+ private CharSequence mCancelLabel;
+
/**
* Create a {@link android.app.Notification.Action.WearableExtender} with default
* options.
@@ -1138,6 +1147,9 @@ public class Notification implements Parcelable
Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
if (wearableBundle != null) {
mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
+ mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL);
+ mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL);
+ mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL);
}
}
@@ -1153,6 +1165,15 @@ public class Notification implements Parcelable
if (mFlags != DEFAULT_FLAGS) {
wearableBundle.putInt(KEY_FLAGS, mFlags);
}
+ if (mInProgressLabel != null) {
+ wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel);
+ }
+ if (mConfirmLabel != null) {
+ wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel);
+ }
+ if (mCancelLabel != null) {
+ wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel);
+ }
builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
return builder;
@@ -1162,6 +1183,9 @@ public class Notification implements Parcelable
public WearableExtender clone() {
WearableExtender that = new WearableExtender();
that.mFlags = this.mFlags;
+ that.mInProgressLabel = this.mInProgressLabel;
+ that.mConfirmLabel = this.mConfirmLabel;
+ that.mCancelLabel = this.mCancelLabel;
return that;
}
@@ -1193,6 +1217,72 @@ public class Notification implements Parcelable
mFlags &= ~mask;
}
}
+
+ /**
+ * Set a label to display while the wearable is preparing to automatically execute the
+ * action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
+ *
+ * @param label the label to display while the action is being prepared to execute
+ * @return this object for method chaining
+ */
+ public WearableExtender setInProgressLabel(CharSequence label) {
+ mInProgressLabel = label;
+ return this;
+ }
+
+ /**
+ * Get the label to display while the wearable is preparing to automatically execute
+ * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..."
+ *
+ * @return the label to display while the action is being prepared to execute
+ */
+ public CharSequence getInProgressLabel() {
+ return mInProgressLabel;
+ }
+
+ /**
+ * Set a label to display to confirm that the action should be executed.
+ * This is usually an imperative verb like "Send".
+ *
+ * @param label the label to confirm the action should be executed
+ * @return this object for method chaining
+ */
+ public WearableExtender setConfirmLabel(CharSequence label) {
+ mConfirmLabel = label;
+ return this;
+ }
+
+ /**
+ * Get the label to display to confirm that the action should be executed.
+ * This is usually an imperative verb like "Send".
+ *
+ * @return the label to confirm the action should be executed
+ */
+ public CharSequence getConfirmLabel() {
+ return mConfirmLabel;
+ }
+
+ /**
+ * Set a label to display to cancel the action.
+ * This is usually an imperative verb, like "Cancel".
+ *
+ * @param label the label to display to cancel the action
+ * @return this object for method chaining
+ */
+ public WearableExtender setCancelLabel(CharSequence label) {
+ mCancelLabel = label;
+ return this;
+ }
+
+ /**
+ * Get the label to display to cancel the action.
+ * This is usually an imperative verb like "Cancel".
+ *
+ * @return the label to display to cancel the action
+ */
+ public CharSequence getCancelLabel() {
+ return mCancelLabel;
+ }
}
}
@@ -2774,6 +2864,14 @@ public class Notification implements Parcelable
contentView.setViewVisibility(R.id.progress, View.VISIBLE);
contentView.setProgressBar(
R.id.progress, mProgressMax, mProgress, mProgressIndeterminate);
+ contentView.setProgressBackgroundTintList(
+ R.id.progress, ColorStateList.valueOf(mContext.getResources().getColor(
+ R.color.notification_progress_background_color)));
+ if (mColor != COLOR_DEFAULT) {
+ ColorStateList colorStateList = ColorStateList.valueOf(mColor);
+ contentView.setProgressTintList(R.id.progress, colorStateList);
+ contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
+ }
showLine2 = true;
} else {
contentView.setViewVisibility(R.id.progress, View.GONE);
@@ -2958,6 +3056,11 @@ public class Notification implements Parcelable
* Apply any necessary background to smallIcons being used in the largeIcon spot.
*/
private void processSmallIconAsLarge(int largeIconId, RemoteViews contentView) {
+ if (!isLegacy()) {
+ contentView.setDrawableParameters(R.id.icon, false, -1,
+ 0xFFFFFFFF,
+ PorterDuff.Mode.SRC_ATOP, -1);
+ }
if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, largeIconId)) {
applyLargeIconBackground(contentView);
}
@@ -3005,11 +3108,12 @@ public class Notification implements Parcelable
*/
private void processSmallRightIcon(int smallIconDrawableId,
RemoteViews contentView) {
- if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, smallIconDrawableId)) {
+ if (!isLegacy()) {
contentView.setDrawableParameters(R.id.right_icon, false, -1,
0xFFFFFFFF,
PorterDuff.Mode.SRC_ATOP, -1);
-
+ }
+ if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, smallIconDrawableId)) {
contentView.setInt(R.id.right_icon,
"setBackgroundResource",
R.drawable.notification_icon_legacy_bg);
@@ -3674,8 +3778,24 @@ public class Notification implements Parcelable
}
private RemoteViews makeBigContentView() {
+
+ // Replace mLargeIcon with mBigLargeIcon if mBigLargeIconSet
+ // This covers the following cases:
+ // 1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
+ // mLargeIcon
+ // 2. !mBigLargeIconSet -> mLargeIcon applies
+ Bitmap oldLargeIcon = null;
+ if (mBigLargeIconSet) {
+ oldLargeIcon = mBuilder.mLargeIcon;
+ mBuilder.mLargeIcon = mBigLargeIcon;
+ }
+
RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
+ if (mBigLargeIconSet) {
+ mBuilder.mLargeIcon = oldLargeIcon;
+ }
+
contentView.setImageViewBitmap(R.id.big_picture, mPicture);
applyTopPadding(contentView);
@@ -3706,6 +3826,7 @@ public class Notification implements Parcelable
super.restoreFromExtras(extras);
if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) {
+ mBigLargeIconSet = true;
mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG);
}
mPicture = extras.getParcelable(EXTRA_PICTURE);
@@ -4001,7 +4122,7 @@ public class Notification implements Parcelable
*
* 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
+ * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed
* in the standard view alongside the usual content.
*
* Notifications created with MediaStyle will have their category set to
@@ -4018,9 +4139,9 @@ public class Notification implements Parcelable
* <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)
+ * .setContentTitle(&quot;Track title&quot;)
+ * .setContentText(&quot;Artist - Album&quot;)
+ * .setLargeIcon(albumArtBitmap))
* .setStyle(<b>new Notification.MediaStyle()</b>
* .setMediaSession(mySession))
* .build();
@@ -4332,10 +4453,23 @@ public class Notification implements Parcelable
*/
public static final int SIZE_FULL_SCREEN = 5;
+ /**
+ * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
+ * short amount of time when this notification is displayed on the screen. This
+ * is the default value.
+ */
+ public static final int SCREEN_TIMEOUT_SHORT = 0;
+
+ /**
+ * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
+ * for a longer amount of time when this notification is displayed on the screen.
+ */
+ public static final int SCREEN_TIMEOUT_LONG = -1;
+
/** Notification extra which contains wearable extensions */
private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
- // Keys within EXTRA_WEARABLE_OPTIONS for wearable options.
+ // Keys within EXTRA_WEARABLE_EXTENSIONS 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";
@@ -4347,12 +4481,14 @@ public class Notification implements Parcelable
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";
+ private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
// 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;
+ private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
// Default value for flags integer
private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
@@ -4371,6 +4507,7 @@ public class Notification implements Parcelable
private int mCustomSizePreset = SIZE_DEFAULT;
private int mCustomContentHeight;
private int mGravity = DEFAULT_GRAVITY;
+ private int mHintScreenTimeout;
/**
* Create a {@link android.app.Notification.WearableExtender} with default
@@ -4406,6 +4543,7 @@ public class Notification implements Parcelable
SIZE_DEFAULT);
mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
+ mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
}
}
@@ -4453,6 +4591,9 @@ public class Notification implements Parcelable
if (mGravity != DEFAULT_GRAVITY) {
wearableBundle.putInt(KEY_GRAVITY, mGravity);
}
+ if (mHintScreenTimeout != 0) {
+ wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
+ }
builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
return builder;
@@ -4472,6 +4613,7 @@ public class Notification implements Parcelable
that.mCustomSizePreset = this.mCustomSizePreset;
that.mCustomContentHeight = this.mCustomContentHeight;
that.mGravity = this.mGravity;
+ that.mHintScreenTimeout = this.mHintScreenTimeout;
return that;
}
@@ -4867,6 +5009,52 @@ public class Notification implements Parcelable
return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
}
+ /**
+ * Set a hint that this notification's background should not be clipped if possible,
+ * and should instead be resized to fully display on the screen, retaining the aspect
+ * ratio of the image. This can be useful for images like barcodes or qr codes.
+ * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
+ * @return this object for method chaining
+ */
+ public WearableExtender setHintAvoidBackgroundClipping(
+ boolean hintAvoidBackgroundClipping) {
+ setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
+ return this;
+ }
+
+ /**
+ * Get a hint that this notification's background should not be clipped if possible,
+ * and should instead be resized to fully display on the screen, retaining the aspect
+ * ratio of the image. This can be useful for images like barcodes or qr codes.
+ * @return {@code true} if it's ok if the background is clipped on the screen, false
+ * otherwise. The default value is {@code false} if this was never set.
+ */
+ public boolean getHintAvoidBackgroundClipping() {
+ return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
+ }
+
+ /**
+ * Set a hint that the screen should remain on for at least this duration when
+ * this notification is displayed on the screen.
+ * @param timeout The requested screen timeout in milliseconds. Can also be either
+ * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
+ * @return this object for method chaining
+ */
+ public WearableExtender setHintScreenTimeout(int timeout) {
+ mHintScreenTimeout = timeout;
+ return this;
+ }
+
+ /**
+ * Get the duration, in milliseconds, that the screen should remain on for
+ * when this notification is displayed.
+ * @return the duration in milliseconds if > 0, or either one of the sentinel values
+ * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
+ */
+ public int getHintScreenTimeout() {
+ return mHintScreenTimeout;
+ }
+
private void setFlag(int mask, boolean value) {
if (value) {
mFlags |= mask;
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 7dc1ad6..cf54107 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -264,5 +264,17 @@ public class NotificationManager
}
}
+ /**
+ * @hide
+ */
+ public boolean isSystemConditionProviderEnabled(String path) {
+ INotificationManager service = getService();
+ try {
+ return service.isSystemConditionProviderEnabled(path);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
private Context mContext;
}
diff --git a/core/java/android/app/SharedElementCallback.java b/core/java/android/app/SharedElementCallback.java
index 060bbe6..6ac2401 100644
--- a/core/java/android/app/SharedElementCallback.java
+++ b/core/java/android/app/SharedElementCallback.java
@@ -18,13 +18,16 @@ package android.app;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.os.Parcelable;
import android.transition.TransitionUtils;
import android.view.View;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
import java.util.List;
import java.util.Map;
@@ -40,6 +43,9 @@ import java.util.Map;
*/
public abstract class SharedElementCallback {
private Matrix mTempMatrix;
+ private static final String BUNDLE_SNAPSHOT_BITMAP = "sharedElement:snapshot:bitmap";
+ private static final String BUNDLE_SNAPSHOT_IMAGE_SCALETYPE = "sharedElement:snapshot:imageScaleType";
+ private static final String BUNDLE_SNAPSHOT_IMAGE_MATRIX = "sharedElement:snapshot:imageMatrix";
static final SharedElementCallback NULL_CALLBACK = new SharedElementCallback() {
};
@@ -142,6 +148,27 @@ public abstract class SharedElementCallback {
*/
public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix,
RectF screenBounds) {
+ if (sharedElement instanceof ImageView) {
+ ImageView imageView = ((ImageView) sharedElement);
+ Drawable d = imageView.getDrawable();
+ Drawable bg = imageView.getBackground();
+ if (d != null && (bg == null || bg.getAlpha() == 0)) {
+ Bitmap bitmap = TransitionUtils.createDrawableBitmap(d);
+ if (bitmap != null) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(BUNDLE_SNAPSHOT_BITMAP, bitmap);
+ bundle.putString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE,
+ imageView.getScaleType().toString());
+ if (imageView.getScaleType() == ScaleType.MATRIX) {
+ Matrix matrix = imageView.getImageMatrix();
+ float[] values = new float[9];
+ matrix.getValues(values);
+ bundle.putFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX, values);
+ }
+ return bundle;
+ }
+ }
+ }
if (mTempMatrix == null) {
mTempMatrix = new Matrix(viewToGlobalMatrix);
} else {
@@ -169,7 +196,24 @@ public abstract class SharedElementCallback {
*/
public View onCreateSnapshotView(Context context, Parcelable snapshot) {
View view = null;
- if (snapshot instanceof Bitmap) {
+ if (snapshot instanceof Bundle) {
+ Bundle bundle = (Bundle) snapshot;
+ Bitmap bitmap = (Bitmap) bundle.getParcelable(BUNDLE_SNAPSHOT_BITMAP);
+ if (bitmap == null) {
+ return null;
+ }
+ ImageView imageView = new ImageView(context);
+ view = imageView;
+ imageView.setImageBitmap(bitmap);
+ imageView.setScaleType(
+ ScaleType.valueOf(bundle.getString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE)));
+ if (imageView.getScaleType() == ScaleType.MATRIX) {
+ float[] values = bundle.getFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX);
+ Matrix matrix = new Matrix();
+ matrix.setValues(values);
+ imageView.setImageMatrix(matrix);
+ }
+ } else if (snapshot instanceof Bitmap) {
Bitmap bitmap = (Bitmap) snapshot;
view = new View(context);
Resources resources = context.getResources();
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index 6454367..3a2c21b 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -134,6 +134,9 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener,
mTimePicker.getCurrentMinute());
}
break;
+ case BUTTON_NEGATIVE:
+ cancel();
+ break;
}
}
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 4aec9e0..b0dd70f 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -25,6 +25,7 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;
import android.hardware.display.DisplayManagerGlobal;
+import android.os.IBinder;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -919,7 +920,7 @@ public final class UiAutomation {
public IAccessibilityServiceClientImpl(Looper looper) {
super(null, looper, new Callbacks() {
@Override
- public void onSetConnectionId(int connectionId) {
+ public void init(int connectionId, IBinder windowToken) {
synchronized (mLock) {
mConnectionId = connectionId;
mLock.notifyAll();
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 8bfe6d3..90d84ee 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -994,6 +994,47 @@ public class WallpaperManager {
}
/**
+ * Clear the wallpaper.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void clearWallpaper() {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ return;
+ }
+ try {
+ sGlobals.mService.clearWallpaper();
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+
+ /**
+ * Set the live wallpaper.
+ *
+ * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
+ * permission.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean setWallpaperComponent(ComponentName name) {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ return false;
+ }
+ try {
+ sGlobals.mService.setWallpaperComponent(name);
+ return true;
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ return false;
+ }
+
+ /**
* Set the position of the current wallpaper within any larger space, when
* that wallpaper is visible behind the given window. The X and Y offsets
* are floating point numbers ranging from 0 to 1, representing where the
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index e9cce51..381d851 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -170,10 +170,10 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
* lock task mode from an authorized package. The extra {@link #EXTRA_LOCK_TASK_PACKAGE}
* will describe the authorized package using lock task mode.
*
- * @see DevicePolicyManager#isLockTaskPermitted(String)
- *
* <p>The calling device admin must be the device owner or profile
* owner to receive this broadcast.
+ *
+ * @see DevicePolicyManager#isLockTaskPermitted(String)
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_LOCK_TASK_ENTERING
@@ -183,20 +183,19 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
* Action sent to a device administrator to notify that the device is exiting
* lock task mode from an authorized package.
*
- * @see DevicePolicyManager#isLockTaskPermitted(String)
- *
* <p>The calling device admin must be the device owner or profile
* owner to receive this broadcast.
+ *
+ * @see DevicePolicyManager#isLockTaskPermitted(String)
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_LOCK_TASK_EXITING
= "android.app.action.LOCK_TASK_EXITING";
/**
- * A boolean describing whether the device is currently entering or exiting
- * lock task mode.
+ * A string containing the name of the package entering lock task mode.
*
- * @see #ACTION_LOCK_TASK_CHANGED
+ * @see #ACTION_LOCK_TASK_ENTERING
*/
public static final String EXTRA_LOCK_TASK_PACKAGE =
"android.app.extra.LOCK_TASK_PACKAGE";
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a30ae57..cf06cb7 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -31,6 +31,7 @@ import android.content.pm.ResolveInfo;
import android.net.ProxyInfo;
import android.os.Bundle;
import android.os.Handler;
+import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
@@ -40,6 +41,7 @@ import android.os.UserManager;
import android.provider.Settings;
import android.security.Credentials;
import android.service.restrictions.RestrictionsReceiver;
+import android.service.trust.TrustAgentService;
import android.util.Log;
import com.android.org.conscrypt.TrustedCertificateStore;
@@ -60,13 +62,16 @@ import java.util.Collections;
import java.util.List;
/**
- * Public interface for managing policies enforced on a device. Most clients
- * of this class must have published a {@link DeviceAdminReceiver} that the user
- * has currently enabled.
+ * Public interface for managing policies enforced on a device. Most clients of this class must be
+ * registered with the system as a
+ * <a href={@docRoot}guide/topics/admin/device-admin.html">device administrator</a>. Additionally,
+ * a device administrator may be registered as either a profile or device owner. A given method is
+ * accessible to all device administrators unless the documentation for that method specifies that
+ * it is restricted to either device or profile owners.
*
* <div class="special reference">
* <h3>Developer Guides</h3>
- * <p>For more information about managing policies for device adminstration, read the
+ * <p>For more information about managing policies for device administration, read the
* <a href="{@docRoot}guide/topics/admin/device-admin.html">Device Administration</a>
* developer guide.</p>
* </div>
@@ -137,13 +142,24 @@ public class DevicePolicyManager {
* {@link #ACTION_PROVISION_MANAGED_PROFILE} this package has to match the package name of the
* application that started provisioning. The package will be set as profile owner in that case.
*
- * <p>This package is set as device owner when device owner provisioning is started by an Nfc
- * message containing an Nfc record with MIME type {@link #MIME_TYPE_PROVISIONING_NFC}.
+ * <p>This package is set as device owner when device owner provisioning is started by an NFC
+ * message containing an NFC record with MIME type {@link #MIME_TYPE_PROVISIONING_NFC}.
*/
public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME
= "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME";
/**
+ * An {@link android.accounts.Account} extra holding the account to migrate during managed
+ * profile provisioning. If the account supplied is present in the primary user, it will be
+ * copied, along with its credentials to the managed profile and removed from the primary user.
+ *
+ * Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}.
+ */
+
+ public static final String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE
+ = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
+
+ /**
* A String extra that, holds the email address of the account which a managed profile is
* created for. Used with {@link #ACTION_PROVISION_MANAGED_PROFILE} and
* {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE}.
@@ -160,11 +176,21 @@ public class DevicePolicyManager {
= "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
/**
+ * A Boolean extra that can be used by the mobile device management application to skip the
+ * disabling of system apps during provisioning when set to <code>true</code>.
+ *
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
+ */
+ public static final String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED =
+ "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
+
+ /**
* A String extra holding the time zone {@link android.app.AlarmManager} that the device
* will be set to.
*
- * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
- * provisioning via an Nfc bump.
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
*/
public static final String EXTRA_PROVISIONING_TIME_ZONE
= "android.app.extra.PROVISIONING_TIME_ZONE";
@@ -173,8 +199,8 @@ public class DevicePolicyManager {
* A Long extra holding the wall clock time (in milliseconds) to be set on the device's
* {@link android.app.AlarmManager}.
*
- * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
- * provisioning via an Nfc bump.
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
*/
public static final String EXTRA_PROVISIONING_LOCAL_TIME
= "android.app.extra.PROVISIONING_LOCAL_TIME";
@@ -183,8 +209,8 @@ public class DevicePolicyManager {
* A String extra holding the {@link java.util.Locale} that the device will be set to.
* Format: xx_yy, where xx is the language code, and yy the country code.
*
- * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
- * provisioning via an Nfc bump.
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
*/
public static final String EXTRA_PROVISIONING_LOCALE
= "android.app.extra.PROVISIONING_LOCALE";
@@ -193,8 +219,8 @@ public class DevicePolicyManager {
* A String extra holding the ssid of the wifi network that should be used during nfc device
* owner provisioning for downloading the mobile device management application.
*
- * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
- * provisioning via an Nfc bump.
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
*/
public static final String EXTRA_PROVISIONING_WIFI_SSID
= "android.app.extra.PROVISIONING_WIFI_SSID";
@@ -203,8 +229,8 @@ public class DevicePolicyManager {
* A boolean extra indicating whether the wifi network in {@link #EXTRA_PROVISIONING_WIFI_SSID}
* is hidden or not.
*
- * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
- * provisioning via an Nfc bump.
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
*/
public static final String EXTRA_PROVISIONING_WIFI_HIDDEN
= "android.app.extra.PROVISIONING_WIFI_HIDDEN";
@@ -213,8 +239,8 @@ public class DevicePolicyManager {
* A String extra indicating the security type of the wifi network in
* {@link #EXTRA_PROVISIONING_WIFI_SSID}.
*
- * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
- * provisioning via an Nfc bump.
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
*/
public static final String EXTRA_PROVISIONING_WIFI_SECURITY_TYPE
= "android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE";
@@ -223,8 +249,8 @@ public class DevicePolicyManager {
* A String extra holding the password of the wifi network in
* {@link #EXTRA_PROVISIONING_WIFI_SSID}.
*
- * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
- * provisioning via an Nfc bump.
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
*/
public static final String EXTRA_PROVISIONING_WIFI_PASSWORD
= "android.app.extra.PROVISIONING_WIFI_PASSWORD";
@@ -233,8 +259,8 @@ public class DevicePolicyManager {
* A String extra holding the proxy host for the wifi network in
* {@link #EXTRA_PROVISIONING_WIFI_SSID}.
*
- * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
- * provisioning via an Nfc bump.
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
*/
public static final String EXTRA_PROVISIONING_WIFI_PROXY_HOST
= "android.app.extra.PROVISIONING_WIFI_PROXY_HOST";
@@ -243,8 +269,8 @@ public class DevicePolicyManager {
* An int extra holding the proxy port for the wifi network in
* {@link #EXTRA_PROVISIONING_WIFI_SSID}.
*
- * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
- * provisioning via an Nfc bump.
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
*/
public static final String EXTRA_PROVISIONING_WIFI_PROXY_PORT
= "android.app.extra.PROVISIONING_WIFI_PROXY_PORT";
@@ -253,8 +279,8 @@ public class DevicePolicyManager {
* A String extra holding the proxy bypass for the wifi network in
* {@link #EXTRA_PROVISIONING_WIFI_SSID}.
*
- * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
- * provisioning via an Nfc bump.
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
*/
public static final String EXTRA_PROVISIONING_WIFI_PROXY_BYPASS
= "android.app.extra.PROVISIONING_WIFI_PROXY_BYPASS";
@@ -263,8 +289,8 @@ public class DevicePolicyManager {
* A String extra holding the proxy auto-config (PAC) URL for the wifi network in
* {@link #EXTRA_PROVISIONING_WIFI_SSID}.
*
- * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
- * provisioning via an Nfc bump.
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
*/
public static final String EXTRA_PROVISIONING_WIFI_PAC_URL
= "android.app.extra.PROVISIONING_WIFI_PAC_URL";
@@ -273,8 +299,8 @@ public class DevicePolicyManager {
* A String extra holding a url that specifies the download location of the device admin
* package. When not provided it is assumed that the device admin package is already installed.
*
- * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
- * provisioning via an Nfc bump.
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
*/
public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION
= "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION";
@@ -283,8 +309,8 @@ public class DevicePolicyManager {
* A String extra holding a http cookie header which should be used in the http request to the
* url specified in {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}.
*
- * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
- * provisioning via an Nfc bump.
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
*/
public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER
= "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER";
@@ -295,8 +321,8 @@ public class DevicePolicyManager {
* the file at the download location an error will be shown to the user and the user will be
* asked to factory reset the device.
*
- * <p>Use in an Nfc record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
- * provisioning via an Nfc bump.
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
*/
public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM
= "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM";
@@ -312,9 +338,9 @@ public class DevicePolicyManager {
* <p> A typical use case would be a device that is owned by a company, but used by either an
* employee or client.
*
- * <p> The Nfc message should be send to an unprovisioned device.
+ * <p> The NFC message should be send to an unprovisioned device.
*
- * <p>The Nfc record must contain a serialized {@link java.util.Properties} object which
+ * <p>The NFC record must contain a serialized {@link java.util.Properties} object which
* contains the following properties:
* <ul>
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}</li>
@@ -439,20 +465,20 @@ public class DevicePolicyManager {
= "android.app.action.SET_NEW_PASSWORD";
/**
- * Flag used by {@link #addCrossProfileIntentFilter} to allow access
- * <em>from</em> a managed profile <em>to</em> its parent. That is, any
- * matching activities in the parent profile are included in the
- * disambiguation list shown when an app in the managed profile calls
- * {@link Activity#startActivity(Intent)}.
+ * Flag used by {@link #addCrossProfileIntentFilter} to allow activities in
+ * the parent profile to access intents sent from the managed profile.
+ * That is, when an app in the managed profile calls
+ * {@link Activity#startActivity(Intent)}, the intent can be resolved by a
+ * matching activity in the parent profile.
*/
public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 0x0001;
/**
- * Flag used by {@link #addCrossProfileIntentFilter} to allow access
- * <em>from</em> a parent <em>to</em> its managed profile. That is, any
- * matching activities in the managed profile are included in the
- * disambiguation list shown when an app in the parent profile calls
- * {@link Activity#startActivity(Intent)}.
+ * Flag used by {@link #addCrossProfileIntentFilter} to allow activities in
+ * the managed profile to access intents sent from the parent profile.
+ * That is, when an app in the parent profile calls
+ * {@link Activity#startActivity(Intent)}, the intent can be resolved by a
+ * matching activity in the managed profile.
*/
public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 0x0002;
@@ -478,6 +504,22 @@ public class DevicePolicyManager {
}
return false;
}
+ /**
+ * Return true if the given administrator component is currently being removed
+ * for the user.
+ * @hide
+ */
+ public boolean isRemovingAdmin(ComponentName who, int userId) {
+ if (mService != null) {
+ try {
+ return mService.isRemovingAdmin(who, userId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return false;
+ }
+
/**
* Return a list of all currently active device administrator's component
@@ -1319,6 +1361,24 @@ public class DevicePolicyManager {
}
/**
+ * Returns the profile with the smallest maximum failed passwords for wipe,
+ * for the given user. So for primary user, it might return the primary or
+ * a managed profile. For a secondary user, it would be the same as the
+ * user passed in.
+ * @hide Used only by Keyguard
+ */
+ public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle) {
+ if (mService != null) {
+ try {
+ return mService.getProfileWithMinimumFailedPasswordsForWipe(userHandle);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return UserHandle.USER_NULL;
+ }
+
+ /**
* Flag for {@link #resetPassword}: don't allow other admins to change
* the password again until the user has entered it.
*/
@@ -1337,13 +1397,16 @@ public class DevicePolicyManager {
* characters when the requested quality is only numeric), in which case
* the currently active quality will be increased to match.
*
+ * <p>Calling with a null or empty password will clear any existing PIN,
+ * pattern or password if the current password constraints allow it.
+ *
* <p>The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} to be able to call
* this method; if it has not, a security exception will be thrown.
*
* <p>Calling this from a managed profile will throw a security exception.
*
- * @param password The new password for the user.
+ * @param password The new password for the user. Null or empty clears the password.
* @param flags May be 0 or {@link #RESET_PASSWORD_REQUIRE_ENTRY}.
* @return Returns true if the password was applied, or false if it is
* not acceptable for the current constraints.
@@ -1426,22 +1489,30 @@ public class DevicePolicyManager {
/**
* Flag for {@link #wipeData(int)}: also erase the device's external
- * storage.
+ * storage (such as SD cards).
*/
public static final int WIPE_EXTERNAL_STORAGE = 0x0001;
/**
+ * Flag for {@link #wipeData(int)}: also erase the factory reset protection
+ * data.
+ *
+ * <p>This flag may only be set by device owner admins; if it is set by
+ * other admins a {@link SecurityException} will be thrown.
+ */
+ public static final int WIPE_RESET_PROTECTION_DATA = 0x0002;
+
+ /**
* Ask the user data be wiped. This will cause the device to reboot,
- * erasing all user data while next booting up. External storage such
- * as SD cards will be also erased if the flag {@link #WIPE_EXTERNAL_STORAGE}
- * is set.
+ * erasing all user data while next booting up.
*
* <p>The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to be able to call
* this method; if it has not, a security exception will be thrown.
*
- * @param flags Bit mask of additional options: currently 0 and
- * {@link #WIPE_EXTERNAL_STORAGE} are supported.
+ * @param flags Bit mask of additional options: currently supported flags
+ * are {@link #WIPE_EXTERNAL_STORAGE} and
+ * {@link #WIPE_RESET_PROTECTION_DATA}.
*/
public void wipeData(int flags) {
if (mService != null) {
@@ -2584,6 +2655,10 @@ public class DevicePolicyManager {
* <p>The application restrictions are only made visible to the target application and the
* profile or device owner.
*
+ * <p>If the restrictions are not available yet, but may be applied in the near future,
+ * the admin can notify the target application of that by adding
+ * {@link UserManager#KEY_RESTRICTIONS_PENDING} to the settings parameter.
+ *
* <p>The calling device admin must be a profile or device owner; if it is not, a security
* exception will be thrown.
*
@@ -2591,6 +2666,8 @@ public class DevicePolicyManager {
* @param packageName The name of the package to update restricted settings for.
* @param settings A {@link Bundle} to be parsed by the receiving application, conveying a new
* set of active restrictions.
+ *
+ * @see UserManager#KEY_RESTRICTIONS_PENDING
*/
public void setApplicationRestrictions(ComponentName admin, String packageName,
Bundle settings) {
@@ -2604,25 +2681,30 @@ public class DevicePolicyManager {
}
/**
- * Sets a list of features to enable for a TrustAgent component. This is meant to be
- * used in conjunction with {@link #KEYGUARD_DISABLE_TRUST_AGENTS}, which will disable all
- * trust agents but those with features enabled by this function call.
+ * Sets a list of configuration features to enable for a TrustAgent component. This is meant
+ * to be used in conjunction with {@link #KEYGUARD_DISABLE_TRUST_AGENTS}, which disables all
+ * trust agents but those enabled by this function call. If flag
+ * {@link #KEYGUARD_DISABLE_TRUST_AGENTS} is not set, then this call has no effect.
*
* <p>The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES} to be able to call
- * this method; if it has not, a security exception will be thrown.
+ * this method; if not, a security exception will be thrown.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @param agent Which component to enable features for.
- * @param features List of features to enable. Consult specific TrustAgent documentation for
- * the feature list.
- * @hide
+ * @param target Component name of the agent to be enabled.
+ * @param configuration TrustAgent-specific feature bundle. If null for any admin, agent
+ * will be strictly disabled according to the state of the
+ * {@link #KEYGUARD_DISABLE_TRUST_AGENTS} flag.
+ * <p>If {@link #KEYGUARD_DISABLE_TRUST_AGENTS} is set and options is not null for all admins,
+ * then it's up to the TrustAgent itself to aggregate the values from all device admins.
+ * <p>Consult documentation for the specific TrustAgent to determine legal options parameters.
*/
- public void setTrustAgentFeaturesEnabled(ComponentName admin, ComponentName agent,
- List<String> features) {
+ public void setTrustAgentConfiguration(ComponentName admin, ComponentName target,
+ PersistableBundle configuration) {
if (mService != null) {
try {
- mService.setTrustAgentFeaturesEnabled(admin, agent, features, UserHandle.myUserId());
+ mService.setTrustAgentConfiguration(admin, target, configuration,
+ UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -2630,24 +2712,35 @@ public class DevicePolicyManager {
}
/**
- * Gets list of enabled features for the given TrustAgent component. If admin is
- * null, this will return the intersection of all features enabled for the given agent by all
- * admins.
+ * Gets configuration for the given trust agent based on aggregating all calls to
+ * {@link #setTrustAgentConfiguration(ComponentName, ComponentName, PersistableBundle)} for
+ * all device admins.
*
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with. If null,
+ * this function returns a list of configurations for all admins that declare
+ * {@link #KEYGUARD_DISABLE_TRUST_AGENTS}. If any admin declares
+ * {@link #KEYGUARD_DISABLE_TRUST_AGENTS} but doesn't call
+ * {@link #setTrustAgentConfiguration(ComponentName, ComponentName, PersistableBundle)}
+ * for this {@param agent} or calls it with a null configuration, null is returned.
* @param agent Which component to get enabled features for.
- * @return List of enabled features.
- * @hide
+ * @return configuration for the given trust agent.
*/
- public List<String> getTrustAgentFeaturesEnabled(ComponentName admin, ComponentName agent) {
+ public List<PersistableBundle> getTrustAgentConfiguration(ComponentName admin,
+ ComponentName agent) {
+ return getTrustAgentConfiguration(admin, agent, UserHandle.myUserId());
+ }
+
+ /** @hide per-user version */
+ public List<PersistableBundle> getTrustAgentConfiguration(ComponentName admin,
+ ComponentName agent, int userHandle) {
if (mService != null) {
try {
- return mService.getTrustAgentFeaturesEnabled(admin, agent, UserHandle.myUserId());
+ return mService.getTrustAgentConfiguration(admin, agent, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
}
- return new ArrayList<String>(); // empty list
+ return new ArrayList<PersistableBundle>(); // empty list
}
/**
@@ -2710,6 +2803,8 @@ public class DevicePolicyManager {
/**
* Called by the profile owner of a managed profile so that some intents sent in the managed
* profile can also be resolved in the parent, or vice versa.
+ * Only activity intents are supported.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param filter The {@link IntentFilter} the intent has to match to be also resolved in the
* other profile
@@ -2838,9 +2933,9 @@ public class DevicePolicyManager {
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param packageNames List of input method package names.
- * @return true if setting the restriction succeeded. It will fail if there is
- * one or more input method enabled, that are not in the list or user if the foreground
- * user.
+ * @return true if setting the restriction succeeded. It will fail if there are
+ * one or more non-system input methods currently enabled that are not in
+ * the packageNames list.
*/
public boolean setPermittedInputMethods(ComponentName admin, List<String> packageNames) {
if (mService != null) {
@@ -3133,9 +3228,10 @@ public class DevicePolicyManager {
}
/**
- * Called by a profile owner to disable account management for a specific type of account.
+ * Called by a device owner or 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
+ * <p>The calling device admin must be a device owner or 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
@@ -3284,6 +3380,7 @@ public class DevicePolicyManager {
* <p>The settings that can be updated by a profile or device owner with this method are:
* <ul>
* <li>{@link Settings.Secure#DEFAULT_INPUT_METHOD}</li>
+ * <li>{@link Settings.Secure#INSTALL_NON_MARKET_APPS}</li>
* <li>{@link Settings.Secure#SKIP_FIRST_USE_HINTS}</li>
* </ul>
* <p>A device owner can additionally update the following settings:
@@ -3377,12 +3474,13 @@ public class DevicePolicyManager {
}
/**
- * Called by profile or device owners to check whether a user has been blocked from
- * uninstalling a package.
+ * Check whether the current user has been blocked by device policy from uninstalling a package.
+ * Requires the caller to be the profile owner if checking a specific admin's policy.
*
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param admin The name of the admin component whose blocking policy will be checked, or null
+ * to check if any admin has blocked the uninstallation.
* @param packageName package to check.
- * @return true if the user shouldn't be able to uninstall the package.
+ * @return true if uninstallation is blocked.
*/
public boolean isUninstallBlocked(ComponentName admin, String packageName) {
if (mService != null) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index c8e1780..0ca60c0 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.net.ProxyInfo;
import android.os.Bundle;
+import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.UserHandle;
import java.util.List;
@@ -65,6 +66,7 @@ interface IDevicePolicyManager {
boolean isActivePasswordSufficient(int userHandle);
int getCurrentFailedPasswordAttempts(int userHandle);
+ int getProfileWithMinimumFailedPasswordsForWipe(int userHandle);
void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num, int userHandle);
int getMaximumFailedPasswordsForWipe(in ComponentName admin, int userHandle);
@@ -183,8 +185,10 @@ interface IDevicePolicyManager {
boolean getCrossProfileCallerIdDisabled(in ComponentName who);
boolean getCrossProfileCallerIdDisabledForUser(int userId);
- void setTrustAgentFeaturesEnabled(in ComponentName admin, in ComponentName agent, in List<String> features, int userId);
- List<String> getTrustAgentFeaturesEnabled(in ComponentName admin, in ComponentName agent, int userId);
+ void setTrustAgentConfiguration(in ComponentName admin, in ComponentName agent,
+ in PersistableBundle args, int userId);
+ List<PersistableBundle> getTrustAgentConfiguration(in ComponentName admin,
+ in ComponentName agent, int userId);
boolean addCrossProfileWidgetProvider(in ComponentName admin, String packageName);
boolean removeCrossProfileWidgetProvider(in ComponentName admin, String packageName);
@@ -192,4 +196,6 @@ interface IDevicePolicyManager {
void setAutoTimeRequired(in ComponentName who, int userHandle, boolean required);
boolean getAutoTimeRequired();
+
+ boolean isRemovingAdmin(in ComponentName adminReceiver, int userHandle);
}
diff --git a/core/java/android/app/backup/BackupHelperDispatcher.java b/core/java/android/app/backup/BackupHelperDispatcher.java
index 5466db5..6811532 100644
--- a/core/java/android/app/backup/BackupHelperDispatcher.java
+++ b/core/java/android/app/backup/BackupHelperDispatcher.java
@@ -50,7 +50,6 @@ public class BackupHelperDispatcher {
Header header = new Header();
TreeMap<String,BackupHelper> helpers = (TreeMap<String,BackupHelper>)mHelpers.clone();
FileDescriptor oldStateFD = null;
- FileDescriptor newStateFD = newState.getFileDescriptor();
if (oldState != null) {
oldStateFD = oldState.getFileDescriptor();
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 70bb5e4..e853540 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -213,8 +213,9 @@ public class BackupTransport {
/**
* 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.
+ * transport may send the data immediately, or may buffer it. If this method returns
+ * {@link #TRANSPORT_OK}, {@link #finishBackup} will then 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.
@@ -226,6 +227,8 @@ public class BackupTransport {
* 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_PACKAGE_REJECTED} (to suppress backup of this
+ * specific package, but allow others to proceed),
* {@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)
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 8a44c8e..41ad936 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -291,4 +291,23 @@ interface IBackupManager {
* {@hide}
*/
void opComplete(int token);
+
+ /**
+ * Make the device's backup and restore machinery (in)active. When it is inactive,
+ * the device will not perform any backup operations, nor will it deliver data for
+ * restore, although clients can still safely call BackupManager methods.
+ *
+ * @param whichUser User handle of the defined user whose backup active state
+ * is to be adjusted.
+ * @param makeActive {@code true} when backup services are to be made active;
+ * {@code false} otherwise.
+ */
+ void setBackupServiceActive(int whichUser, boolean makeActive);
+
+ /**
+ * Queries the activity status of backup service as set by {@link #setBackupServiceActive}.
+ * @param whichUser User handle of the defined user whose backup active state
+ * is being queried.
+ */
+ boolean isBackupServiceActive(int whichUser);
}
diff --git a/core/java/android/app/backup/RecentsBackupHelper.java b/core/java/android/app/backup/RecentsBackupHelper.java
new file mode 100644
index 0000000..fd69d20
--- /dev/null
+++ b/core/java/android/app/backup/RecentsBackupHelper.java
@@ -0,0 +1,114 @@
+package android.app.backup;
+
+import android.content.Context;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.util.Slog;
+
+import java.io.File;
+
+/**
+ * Helper for saving/restoring 'recent tasks' infrastructure.
+ * @hide
+ */
+public class RecentsBackupHelper implements BackupHelper {
+ private static final String TAG = "RecentsBackup";
+ private static final boolean DEBUG = false;
+
+ // This must match TaskPersister.TASKS_DIRNAME, but that class is not accessible from here
+ private static final String RECENTS_TASK_DIR = "recent_tasks";
+
+ // Must match TaskPersister.IMAGES_DIRNAME, as above
+ private static final String RECENTS_IMAGE_DIR = "recent_images";
+
+ // At restore time, tasks/thumbnails are placed in these directories alongside
+ // the "live" recents dirs named above.
+ private static final String RECENTS_TASK_RESTORE_DIR = "restored_" + RECENTS_TASK_DIR;
+ private static final String RECENTS_IMAGE_RESTORE_DIR = "restored_" + RECENTS_IMAGE_DIR;
+
+ // Prefixes for tagging the two kinds of recents backup records that we might generate
+ private static final String RECENTS_TASK_KEY = "task:";
+ private static final String RECENTS_IMAGE_KEY = "image:";
+
+ FileBackupHelperBase mTaskFileHelper;
+
+ final File mSystemDir;
+ final File mTasksDir;
+ final File mRestoredTasksDir;
+ final File mRestoredImagesDir;
+ final String[] mRecentFiles;
+ final String[] mRecentKeys;
+
+ /**
+ * @param context The agent context in which this helper instance will run
+ */
+ public RecentsBackupHelper(Context context) {
+ mTaskFileHelper = new FileBackupHelperBase(context);
+
+ mSystemDir = new File(Environment.getDataDirectory(), "system");
+ mTasksDir = new File(mSystemDir, RECENTS_TASK_DIR);
+ mRestoredTasksDir = new File(mSystemDir, RECENTS_TASK_RESTORE_DIR);
+ mRestoredImagesDir = new File(mSystemDir, RECENTS_IMAGE_RESTORE_DIR);
+
+ // Currently we back up only the recent-task descriptions, not the thumbnails
+ File[] recentFiles = mTasksDir.listFiles();
+ if (recentFiles != null) {
+ // We explicitly proceed even if this is a zero-size array
+ final int N = recentFiles.length;
+ mRecentKeys = new String[N];
+ mRecentFiles = new String[N];
+ if (DEBUG) {
+ Slog.i(TAG, "Identifying recents for backup: " + N);
+ }
+ for (int i = 0; i < N; i++) {
+ mRecentKeys[i] = new String(RECENTS_TASK_KEY + recentFiles[i].getName());
+ mRecentFiles[i] = recentFiles[i].getAbsolutePath();
+ if (DEBUG) {
+ Slog.i(TAG, " " + mRecentKeys[i]);
+ }
+ }
+ } else {
+ mRecentFiles = mRecentKeys = new String[0];
+ }
+ }
+
+ /**
+ * Task-file key: RECENTS_TASK_KEY + leaf filename
+ * Thumbnail-file key: RECENTS_IMAGE_KEY + leaf filename
+ */
+ @Override
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) {
+ FileBackupHelperBase.performBackup_checked(oldState, data, newState,
+ mRecentFiles, mRecentKeys);
+ }
+
+ @Override
+ public void restoreEntity(BackupDataInputStream data) {
+ final String key = data.getKey();
+ File output = null;
+ if (key.startsWith(RECENTS_TASK_KEY)) {
+ String name = key.substring(RECENTS_TASK_KEY.length());
+ output = new File(mRestoredTasksDir, name);
+ mRestoredTasksDir.mkdirs();
+ } else if (key.startsWith(RECENTS_IMAGE_KEY)) {
+ String name = key.substring(RECENTS_IMAGE_KEY.length());
+ output = new File(mRestoredImagesDir, name);
+ mRestoredImagesDir.mkdirs();
+ }
+
+ if (output != null) {
+ if (DEBUG) {
+ Slog.i(TAG, "Restoring key='"
+ + key + "' to " + output.getAbsolutePath());
+ }
+ mTaskFileHelper.writeFile(output, data);
+ }
+ }
+
+ @Override
+ public void writeNewStateDescription(ParcelFileDescriptor newState) {
+ mTaskFileHelper.writeNewStateDescription(newState);
+ }
+
+}
diff --git a/core/java/android/app/backup/WallpaperBackupHelper.java b/core/java/android/app/backup/WallpaperBackupHelper.java
index a55cc2b..7a80936 100644
--- a/core/java/android/app/backup/WallpaperBackupHelper.java
+++ b/core/java/android/app/backup/WallpaperBackupHelper.java
@@ -102,9 +102,8 @@ public class WallpaperBackupHelper extends FileBackupHelperBase implements Backu
final Display d = wm.getDefaultDisplay();
final Point size = new Point();
d.getSize(size);
- mDesiredMinWidth = size.x;
+ mDesiredMinWidth = Math.min(size.x, size.y);
mDesiredMinHeight = (double) wpm.getDesiredMinimumHeight();
-
if (mDesiredMinHeight <= 0) {
mDesiredMinHeight = size.y;
}
@@ -149,9 +148,13 @@ public class WallpaperBackupHelper extends FileBackupHelperBase implements Backu
// We accept any wallpaper that is at least as wide as our preference
// (i.e. wide enough to fill the screen), and is within a comfortable
// factor of the target height, to avoid significant clipping/scaling/
- // letterboxing.
+ // letterboxing. At this point we know that mDesiredMinWidth is the
+ // smallest dimension, regardless of current orientation, so we can
+ // safely require that the candidate's width and height both exceed
+ // that hard minimum.
final double heightRatio = mDesiredMinHeight / options.outHeight;
if (options.outWidth < mDesiredMinWidth
+ || options.outHeight < mDesiredMinWidth
|| heightRatio >= MAX_HEIGHT_RATIO
|| heightRatio <= MIN_HEIGHT_RATIO) {
// Not wide enough for the screen, or too short/tall to be a good fit
diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
index 6fbf87d..68ea0aa 100644
--- a/core/java/android/app/trust/ITrustManager.aidl
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -29,4 +29,6 @@ interface ITrustManager {
void reportRequireCredentialEntry(int userId);
void registerTrustListener(in ITrustListener trustListener);
void unregisterTrustListener(in ITrustListener trustListener);
+ void reportKeyguardShowingChanged();
+ boolean isDeviceLocked(int userId);
}
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 3d262b1..705a144 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -88,6 +88,19 @@ public class TrustManager {
}
/**
+ * Reports that the visibility of the keyguard has changed.
+ *
+ * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+ */
+ public void reportKeyguardShowingChanged() {
+ try {
+ mService.reportKeyguardShowingChanged();
+ } catch (RemoteException e) {
+ onError(e);
+ }
+ }
+
+ /**
* Registers a listener for trust events.
*
* Requires the {@link android.Manifest.permission#TRUST_LISTENER} permission.
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 24c6793..8e86824 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -57,7 +57,7 @@ public class AppWidgetHost {
static IAppWidgetService sService;
private DisplayMetrics mDisplayMetrics;
- Context mContext;
+ private String mContextOpPackageName;
Handler mHandler;
int mHostId;
Callbacks mCallbacks = new Callbacks();
@@ -128,7 +128,7 @@ public class AppWidgetHost {
* @hide
*/
public AppWidgetHost(Context context, int hostId, OnClickHandler handler, Looper looper) {
- mContext = context;
+ mContextOpPackageName = context.getOpPackageName();
mHostId = hostId;
mOnClickHandler = handler;
mHandler = new UpdateHandler(looper);
@@ -154,7 +154,7 @@ public class AppWidgetHost {
int[] updatedIds;
ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>();
try {
- updatedIds = sService.startListening(mCallbacks, mContext.getOpPackageName(), mHostId,
+ updatedIds = sService.startListening(mCallbacks, mContextOpPackageName, mHostId,
updatedViews);
}
catch (RemoteException e) {
@@ -173,7 +173,7 @@ public class AppWidgetHost {
*/
public void stopListening() {
try {
- sService.stopListening(mContext.getOpPackageName(), mHostId);
+ sService.stopListening(mContextOpPackageName, mHostId);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -191,7 +191,7 @@ public class AppWidgetHost {
*/
public int allocateAppWidgetId() {
try {
- return sService.allocateAppWidgetId(mContext.getOpPackageName(), mHostId);
+ return sService.allocateAppWidgetId(mContextOpPackageName, mHostId);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -221,7 +221,7 @@ public class AppWidgetHost {
int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options) {
try {
IntentSender intentSender = sService.createAppWidgetConfigIntentSender(
- mContext.getOpPackageName(), appWidgetId, intentFlags);
+ mContextOpPackageName, appWidgetId, intentFlags);
if (intentSender != null) {
activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0,
options);
@@ -245,7 +245,7 @@ public class AppWidgetHost {
if (sService == null) {
bindService();
}
- return sService.getAppWidgetIdsForHost(mContext.getOpPackageName(), mHostId);
+ return sService.getAppWidgetIdsForHost(mContextOpPackageName, mHostId);
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
@@ -262,7 +262,7 @@ public class AppWidgetHost {
synchronized (mViews) {
mViews.remove(appWidgetId);
try {
- sService.deleteAppWidgetId(mContext.getOpPackageName(), appWidgetId);
+ sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -280,7 +280,7 @@ public class AppWidgetHost {
*/
public void deleteHost() {
try {
- sService.deleteHost(mContext.getOpPackageName(), mHostId);
+ sService.deleteHost(mContextOpPackageName, mHostId);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -310,7 +310,7 @@ public class AppWidgetHost {
*/
public final AppWidgetHostView createView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
- AppWidgetHostView view = onCreateView(mContext, appWidgetId, appWidget);
+ AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
view.setOnClickHandler(mOnClickHandler);
view.setAppWidget(appWidgetId, appWidget);
synchronized (mViews) {
@@ -318,7 +318,7 @@ public class AppWidgetHost {
}
RemoteViews views;
try {
- views = sService.getAppWidgetViews(mContext.getOpPackageName(), appWidgetId);
+ views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 5564af7..b8f4bf8 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -465,7 +465,8 @@ public final class BluetoothAdapter {
if (getState() != STATE_ON) {
return null;
}
- if (!isMultipleAdvertisementSupported()) {
+ if (!isMultipleAdvertisementSupported() && !isPeripheralModeSupported()) {
+ Log.e(TAG, "bluetooth le advertising not supported");
return null;
}
synchronized(mLock) {
@@ -918,6 +919,21 @@ public final class BluetoothAdapter {
}
/**
+ * Returns whether peripheral mode is supported.
+ *
+ * @hide
+ */
+ public boolean isPeripheralModeSupported() {
+ if (getState() != STATE_ON) return false;
+ try {
+ return mService.isPeripheralModeSupported();
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to get peripheral mode capability: ", e);
+ }
+ return false;
+ }
+
+ /**
* Return true if offloaded filters are supported
*
* @return true if chipset supports on-chip filtering
@@ -1413,14 +1429,16 @@ public final class BluetoothAdapter {
if (VDBG) Log.d(TAG, "onBluetoothServiceUp: " + bluetoothService);
synchronized (mManagerCallback) {
mService = bluetoothService;
- for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks ){
- try {
- if (cb != null) {
- cb.onBluetoothServiceUp(bluetoothService);
- } else {
- Log.d(TAG, "onBluetoothServiceUp: cb is null!!!");
- }
- } catch (Exception e) { Log.e(TAG,"",e);}
+ synchronized (mProxyServiceStateCallbacks) {
+ for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks ){
+ try {
+ if (cb != null) {
+ cb.onBluetoothServiceUp(bluetoothService);
+ } else {
+ Log.d(TAG, "onBluetoothServiceUp: cb is null!!!");
+ }
+ } catch (Exception e) { Log.e(TAG,"",e);}
+ }
}
}
}
@@ -1429,17 +1447,19 @@ public final class BluetoothAdapter {
if (VDBG) Log.d(TAG, "onBluetoothServiceDown: " + mService);
synchronized (mManagerCallback) {
mService = null;
- mLeScanClients.clear();
+ if (mLeScanClients != null) mLeScanClients.clear();
if (sBluetoothLeAdvertiser != null) sBluetoothLeAdvertiser.cleanup();
if (sBluetoothLeScanner != null) sBluetoothLeScanner.cleanup();
- for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks ){
- try {
- if (cb != null) {
- cb.onBluetoothServiceDown();
- } else {
- Log.d(TAG, "onBluetoothServiceDown: cb is null!!!");
- }
- } catch (Exception e) { Log.e(TAG,"",e);}
+ synchronized (mProxyServiceStateCallbacks) {
+ for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks ){
+ try {
+ if (cb != null) {
+ cb.onBluetoothServiceDown();
+ } else {
+ Log.d(TAG, "onBluetoothServiceDown: cb is null!!!");
+ }
+ } catch (Exception e) { Log.e(TAG,"",e);}
+ }
}
}
}
@@ -1580,10 +1600,10 @@ public final class BluetoothAdapter {
return mManagerService;
}
- private ArrayList<IBluetoothManagerCallback> mProxyServiceStateCallbacks = new ArrayList<IBluetoothManagerCallback>();
+ final private ArrayList<IBluetoothManagerCallback> mProxyServiceStateCallbacks = new ArrayList<IBluetoothManagerCallback>();
/*package*/ IBluetooth getBluetoothService(IBluetoothManagerCallback cb) {
- synchronized (mManagerCallback) {
+ synchronized (mProxyServiceStateCallbacks) {
if (cb == null) {
Log.w(TAG, "getBluetoothService() called with no BluetoothManagerCallback");
} else if (!mProxyServiceStateCallbacks.contains(cb)) {
@@ -1594,7 +1614,7 @@ public final class BluetoothAdapter {
}
/*package*/ void removeServiceStateCallback(IBluetoothManagerCallback cb) {
- synchronized (mManagerCallback) {
+ synchronized (mProxyServiceStateCallbacks) {
mProxyServiceStateCallbacks.remove(cb);
}
}
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 5e50b69..bb0d0a3 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -18,6 +18,7 @@ package android.bluetooth;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
@@ -67,6 +68,14 @@ public final class BluetoothDevice implements Parcelable {
private static final boolean DBG = false;
/**
+ * Connection state bitmask as returned by getConnectionState.
+ */
+ private static final int CONNECTION_STATE_DISCONNECTED = 0;
+ private static final int CONNECTION_STATE_CONNECTED = 1;
+ private static final int CONNECTION_STATE_ENCRYPTED_BREDR = 2;
+ private static final int CONNECTION_STATE_ENCRYPTED_LE = 4;
+
+ /**
* Sentinel error value for this class. Guaranteed to not equal any other
* integer constant in this class. Provided as a convenience for functions
* that require a sentinel error value, for example:
@@ -940,13 +949,36 @@ public final class BluetoothDevice implements Parcelable {
* @return True if there is at least one open connection to this device.
* @hide
*/
+ @SystemApi
public boolean isConnected() {
if (sService == null) {
// BT is not enabled, we cannot be connected.
return false;
}
try {
- return sService.isConnected(this);
+ return sService.getConnectionState(this) != CONNECTION_STATE_DISCONNECTED;
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ return false;
+ }
+ }
+
+ /**
+ * Returns whether there is an open connection to this device
+ * that has been encrypted.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
+ *
+ * @return True if there is at least one encrypted connection to this device.
+ * @hide
+ */
+ @SystemApi
+ public boolean isEncrypted() {
+ if (sService == null) {
+ // BT is not enabled, we cannot be connected.
+ return false;
+ }
+ try {
+ return sService.getConnectionState(this) > CONNECTION_STATE_CONNECTED;
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index c203a8e..ea2dca0 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -928,7 +928,7 @@ public final class BluetoothGatt implements BluetoothProfile {
BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) return false;
if (VDBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid());
- if (mService == null || mClientIf == 0) return false;
+ if (mService == null || mClientIf == 0 || characteristic.getValue() == null) return false;
BluetoothGattService service = characteristic.getService();
if (service == null) return false;
@@ -1015,7 +1015,7 @@ public final class BluetoothGatt implements BluetoothProfile {
*/
public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
if (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
- if (mService == null || mClientIf == 0) return false;
+ if (mService == null || mClientIf == 0 || descriptor.getValue() == null) return false;
BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
if (characteristic == null) return false;
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
index a86677c..7cdcc2c 100644
--- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -502,7 +502,7 @@ public class BluetoothGattCharacteristic {
* @return Cached value of the characteristic
*/
public String getStringValue(int offset) {
- if (offset > mValue.length) return null;
+ if (mValue == null || offset > mValue.length) return null;
byte[] strBytes = new byte[mValue.length - offset];
for (int i=0; i != (mValue.length-offset); ++i) strBytes[i] = mValue[offset+i];
return new String(strBytes);
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index e94a8ce..f451340 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -284,6 +284,24 @@ public final class BluetoothGattServer implements BluetoothProfile {
Log.w(TAG, "Unhandled exception: " + ex);
}
}
+
+ /**
+ * The MTU for a connection has changed
+ * @hide
+ */
+ public void onMtuChanged(String address, int mtu) {
+ if (DBG) Log.d(TAG, "onMtuChanged() - "
+ + "device=" + address + ", mtu=" + mtu);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ if (device == null) return;
+
+ try {
+ mCallback.onMtuChanged(device, mtu);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
};
/**
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java
index 1dd06f2..2afcf9a 100644
--- a/core/java/android/bluetooth/BluetoothGattServerCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -145,4 +145,16 @@ public abstract class BluetoothGattServerCallback {
*/
public void onNotificationSent(BluetoothDevice device, int status) {
}
+
+ /**
+ * Callback indicating the MTU for a given device connection has changed.
+ *
+ * <p>This callback will be invoked if a remote client has requested to change
+ * the MTU for a given connection.
+ *
+ * @param device The remote device that requested the MTU change
+ * @param mtu The new MTU size
+ */
+ public void onMtuChanged(BluetoothDevice device, int mtu) {
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 353f0fb..25d9aa9 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -20,9 +20,10 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
@@ -221,6 +222,8 @@ public final class BluetoothHeadset implements BluetoothProfile {
*/
public static final int STATE_AUDIO_CONNECTED = 12;
+ private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
+ private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
private Context mContext;
private ServiceListener mServiceListener;
@@ -233,14 +236,7 @@ public final class BluetoothHeadset implements BluetoothProfile {
if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
if (!up) {
if (VDBG) Log.d(TAG,"Unbinding service...");
- synchronized (mConnection) {
- try {
- mService = null;
- mContext.unbindService(mConnection);
- } catch (Exception re) {
- Log.e(TAG,"",re);
- }
- }
+ doUnbind();
} else {
synchronized (mConnection) {
try {
@@ -277,15 +273,26 @@ public final class BluetoothHeadset implements BluetoothProfile {
}
boolean doBind() {
- Intent intent = new Intent(IBluetoothHeadset.class.getName());
- ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
- intent.setComponent(comp);
- if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
- android.os.Process.myUserHandle())) {
- Log.e(TAG, "Could not bind to Bluetooth Headset Service with " + intent);
- return false;
+ try {
+ return mAdapter.getBluetoothManager().bindBluetoothProfileService(
+ BluetoothProfile.HEADSET, mConnection);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to bind HeadsetService", e);
+ }
+ return false;
+ }
+
+ void doUnbind() {
+ synchronized (mConnection) {
+ if (mService != null) {
+ try {
+ mAdapter.getBluetoothManager().unbindBluetoothProfileService(
+ BluetoothProfile.HEADSET, mConnection);
+ } catch (RemoteException e) {
+ Log.e(TAG,"Unable to unbind HeadsetService", e);
+ }
+ }
}
- return true;
}
/**
@@ -305,18 +312,8 @@ public final class BluetoothHeadset implements BluetoothProfile {
Log.e(TAG,"",e);
}
}
-
- synchronized (mConnection) {
- if (mService != null) {
- try {
- mService = null;
- mContext.unbindService(mConnection);
- } catch (Exception re) {
- Log.e(TAG,"",re);
- }
- }
- }
mServiceListener = null;
+ doUnbind();
}
/**
@@ -930,21 +927,21 @@ public final class BluetoothHeadset implements BluetoothProfile {
return false;
}
- private final ServiceConnection mConnection = new ServiceConnection() {
+ private final IBluetoothProfileServiceConnection mConnection
+ = new IBluetoothProfileServiceConnection.Stub() {
+ @Override
public void onServiceConnected(ComponentName className, IBinder service) {
if (DBG) Log.d(TAG, "Proxy object connected");
mService = IBluetoothHeadset.Stub.asInterface(service);
-
- if (mServiceListener != null) {
- mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
- }
+ mHandler.sendMessage(mHandler.obtainMessage(
+ MESSAGE_HEADSET_SERVICE_CONNECTED));
}
+ @Override
public void onServiceDisconnected(ComponentName className) {
if (DBG) Log.d(TAG, "Proxy object disconnected");
mService = null;
- if (mServiceListener != null) {
- mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
- }
+ mHandler.sendMessage(mHandler.obtainMessage(
+ MESSAGE_HEADSET_SERVICE_DISCONNECTED));
}
};
@@ -968,4 +965,25 @@ public final class BluetoothHeadset implements BluetoothProfile {
private static void log(String msg) {
Log.d(TAG, msg);
}
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_HEADSET_SERVICE_CONNECTED: {
+ if (mServiceListener != null) {
+ mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
+ BluetoothHeadset.this);
+ }
+ break;
+ }
+ case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
+ if (mServiceListener != null) {
+ mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
+ }
+ break;
+ }
+ }
+ }
+ };
}
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 1e22eb3..194a53e 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -66,6 +66,8 @@ public final class BluetoothUuid {
ParcelUuid.fromString("00001116-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid BNEP =
ParcelUuid.fromString("0000000f-0000-1000-8000-00805F9B34FB");
+ public static final ParcelUuid PBAP_PCE =
+ ParcelUuid.fromString("0000112e-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid PBAP_PSE =
ParcelUuid.fromString("0000112f-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid MAP =
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index cf2a343..dabb1ce 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -59,7 +59,7 @@ interface IBluetooth
boolean cancelBondProcess(in BluetoothDevice device);
boolean removeBond(in BluetoothDevice device);
int getBondState(in BluetoothDevice device);
- boolean isConnected(in BluetoothDevice device);
+ int getConnectionState(in BluetoothDevice device);
String getRemoteName(in BluetoothDevice device);
int getRemoteType(in BluetoothDevice device);
@@ -92,9 +92,13 @@ interface IBluetooth
boolean configHciSnoopLog(boolean enable);
boolean isMultiAdvertisementSupported();
+ boolean isPeripheralModeSupported();
boolean isOffloadedFilteringSupported();
boolean isOffloadedScanBatchingSupported();
boolean isActivityAndEnergyReportingSupported();
void getActivityEnergyInfoFromController();
BluetoothActivityEnergyInfo reportActivityInfo();
+
+ // for dumpsys support
+ String dump();
}
diff --git a/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
index 5d4d6c6..8b202b2 100644
--- a/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
@@ -59,4 +59,5 @@ oneway interface IBluetoothGattServerCallback {
in byte[] value);
void onExecuteWrite(in String address, in int transId, in boolean execWrite);
void onNotificationSent(in String address, in int status);
+ void onMtuChanged(in String address, in int mtu);
}
diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl
index 493d2f8..7411d3f 100644
--- a/core/java/android/bluetooth/IBluetoothManager.aidl
+++ b/core/java/android/bluetooth/IBluetoothManager.aidl
@@ -19,6 +19,7 @@ package android.bluetooth;
import android.bluetooth.IBluetooth;
import android.bluetooth.IBluetoothGatt;
import android.bluetooth.IBluetoothManagerCallback;
+import android.bluetooth.IBluetoothProfileServiceConnection;
import android.bluetooth.IBluetoothStateChangeCallback;
/**
@@ -38,6 +39,9 @@ interface IBluetoothManager
boolean disable(boolean persist);
IBluetoothGatt getBluetoothGatt();
+ boolean bindBluetoothProfileService(int profile, IBluetoothProfileServiceConnection proxy);
+ void unbindBluetoothProfileService(int profile, IBluetoothProfileServiceConnection proxy);
+
String getAddress();
String getName();
}
diff --git a/core/java/android/bluetooth/IBluetoothProfileServiceConnection.aidl b/core/java/android/bluetooth/IBluetoothProfileServiceConnection.aidl
new file mode 100755
index 0000000..96c59e2
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothProfileServiceConnection.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.bluetooth;
+
+import android.content.ComponentName;
+import android.os.IBinder;
+
+/**
+ * Callback for bluetooth profile connections.
+ *
+ * {@hide}
+ */
+interface IBluetoothProfileServiceConnection {
+ void onServiceConnected(in ComponentName comp, in IBinder service);
+ void onServiceDisconnected(in ComponentName comp);
+}
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index d468508..67d9de5 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -111,13 +111,15 @@ public final class BluetoothLeAdvertiser {
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
- if (!mBluetoothAdapter.isMultipleAdvertisementSupported()) {
+ if (!mBluetoothAdapter.isMultipleAdvertisementSupported() &&
+ !mBluetoothAdapter.isPeripheralModeSupported()) {
postStartFailure(callback,
AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED);
return;
}
- if (totalBytes(advertiseData) > MAX_ADVERTISING_DATA_BYTES ||
- totalBytes(scanResponse) > MAX_ADVERTISING_DATA_BYTES) {
+ boolean isConnectable = settings.isConnectable();
+ if (totalBytes(advertiseData, isConnectable) > MAX_ADVERTISING_DATA_BYTES ||
+ totalBytes(scanResponse, false) > MAX_ADVERTISING_DATA_BYTES) {
postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
return;
}
@@ -169,10 +171,11 @@ public final class BluetoothLeAdvertiser {
mLeAdvertisers.clear();
}
- // Compute the size of the advertise data.
- private int totalBytes(AdvertiseData data) {
+ // Compute the size of advertisement data or scan resp
+ private int totalBytes(AdvertiseData data, boolean isFlagsIncluded) {
if (data == null) return 0;
- int size = FLAGS_FIELD_BYTES; // flags field is always set.
+ // Flags field is omitted if the advertising is not connectable.
+ int size = (isFlagsIncluded) ? FLAGS_FIELD_BYTES : 0;
if (data.getServiceUuids() != null) {
int num16BitUuids = 0;
int num32BitUuids = 0;
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index a57c3ca..93ea299 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -51,6 +51,7 @@ public final class BluetoothLeScanner {
private static final String TAG = "BluetoothLeScanner";
private static final boolean DBG = true;
+ private static final boolean VDBG = false;
private final IBluetoothManager mBluetoothManager;
private final Handler mHandler;
@@ -317,7 +318,7 @@ public final class BluetoothLeScanner {
*/
@Override
public void onScanResult(final ScanResult scanResult) {
- if (DBG) Log.d(TAG, "onScanResult() - " + scanResult.toString());
+ if (VDBG) Log.d(TAG, "onScanResult() - " + scanResult.toString());
// Check null in case the scan has been stopped
synchronized (this) {
@@ -346,7 +347,7 @@ public final class BluetoothLeScanner {
@Override
public void onFoundOrLost(final boolean onFound, final ScanResult scanResult) {
- if (DBG) {
+ if (VDBG) {
Log.d(TAG, "onFoundOrLost() - onFound = " + onFound +
" " + scanResult.toString());
}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 2853c58..0cff4c0 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -31,6 +31,7 @@ import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.OperationCanceledException;
import android.os.ParcelFileDescriptor;
@@ -201,7 +202,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
ICancellationSignal cancellationSignal) {
validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
- if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
return rejectQuery(uri, projection, selection, selectionArgs, sortOrder,
CancellationSignal.fromTransport(cancellationSignal));
}
@@ -227,7 +228,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
validateIncomingUri(uri);
int userId = getUserIdFromUri(uri);
uri = getUriWithoutUserId(uri);
- if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
return rejectInsert(uri, initialValues);
}
final String original = setCallingPackage(callingPkg);
@@ -242,7 +243,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
public int bulkInsert(String callingPkg, Uri uri, ContentValues[] initialValues) {
validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
- if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
return 0;
}
final String original = setCallingPackage(callingPkg);
@@ -270,13 +271,13 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
operations.set(i, operation);
}
if (operation.isReadOperation()) {
- if (enforceReadPermission(callingPkg, uri)
+ if (enforceReadPermission(callingPkg, uri, null)
!= AppOpsManager.MODE_ALLOWED) {
throw new OperationApplicationException("App op not allowed", 0);
}
}
if (operation.isWriteOperation()) {
- if (enforceWritePermission(callingPkg, uri)
+ if (enforceWritePermission(callingPkg, uri, null)
!= AppOpsManager.MODE_ALLOWED) {
throw new OperationApplicationException("App op not allowed", 0);
}
@@ -285,10 +286,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
final String original = setCallingPackage(callingPkg);
try {
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]);
+ if (results != null) {
+ 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;
@@ -301,7 +304,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
public int delete(String callingPkg, Uri uri, String selection, String[] selectionArgs) {
validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
- if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
return 0;
}
final String original = setCallingPackage(callingPkg);
@@ -317,7 +320,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
String[] selectionArgs) {
validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
- if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
return 0;
}
final String original = setCallingPackage(callingPkg);
@@ -330,11 +333,11 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
@Override
public ParcelFileDescriptor openFile(
- String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal)
- throws FileNotFoundException {
+ String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal,
+ IBinder callerToken) throws FileNotFoundException {
validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
- enforceFilePermission(callingPkg, uri, mode);
+ enforceFilePermission(callingPkg, uri, mode, callerToken);
final String original = setCallingPackage(callingPkg);
try {
return ContentProvider.this.openFile(
@@ -350,7 +353,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
throws FileNotFoundException {
validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
- enforceFilePermission(callingPkg, uri, mode);
+ enforceFilePermission(callingPkg, uri, mode, null);
final String original = setCallingPackage(callingPkg);
try {
return ContentProvider.this.openAssetFile(
@@ -382,7 +385,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException {
validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
- enforceFilePermission(callingPkg, uri, "r");
+ enforceFilePermission(callingPkg, uri, "r", null);
final String original = setCallingPackage(callingPkg);
try {
return ContentProvider.this.openTypedAssetFile(
@@ -402,7 +405,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
validateIncomingUri(uri);
int userId = getUserIdFromUri(uri);
uri = getUriWithoutUserId(uri);
- if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
return null;
}
final String original = setCallingPackage(callingPkg);
@@ -418,7 +421,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
validateIncomingUri(uri);
int userId = getUserIdFromUri(uri);
uri = getUriWithoutUserId(uri);
- if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
return null;
}
final String original = setCallingPackage(callingPkg);
@@ -429,29 +432,33 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
}
- private void enforceFilePermission(String callingPkg, Uri uri, String mode)
- throws FileNotFoundException, SecurityException {
+ private void enforceFilePermission(String callingPkg, Uri uri, String mode,
+ IBinder callerToken) throws FileNotFoundException, SecurityException {
if (mode != null && mode.indexOf('w') != -1) {
- if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceWritePermission(callingPkg, uri, callerToken)
+ != AppOpsManager.MODE_ALLOWED) {
throw new FileNotFoundException("App op not allowed");
}
} else {
- if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ if (enforceReadPermission(callingPkg, uri, callerToken)
+ != AppOpsManager.MODE_ALLOWED) {
throw new FileNotFoundException("App op not allowed");
}
}
}
- private int enforceReadPermission(String callingPkg, Uri uri) throws SecurityException {
- enforceReadPermissionInner(uri);
+ private int enforceReadPermission(String callingPkg, Uri uri, IBinder callerToken)
+ throws SecurityException {
+ enforceReadPermissionInner(uri, callerToken);
if (mReadOp != AppOpsManager.OP_NONE) {
return mAppOpsManager.noteOp(mReadOp, Binder.getCallingUid(), callingPkg);
}
return AppOpsManager.MODE_ALLOWED;
}
- private int enforceWritePermission(String callingPkg, Uri uri) throws SecurityException {
- enforceWritePermissionInner(uri);
+ private int enforceWritePermission(String callingPkg, Uri uri, IBinder callerToken)
+ throws SecurityException {
+ enforceWritePermissionInner(uri, callerToken);
if (mWriteOp != AppOpsManager.OP_NONE) {
return mAppOpsManager.noteOp(mWriteOp, Binder.getCallingUid(), callingPkg);
}
@@ -467,7 +474,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
/** {@hide} */
- protected void enforceReadPermissionInner(Uri uri) throws SecurityException {
+ protected void enforceReadPermissionInner(Uri uri, IBinder callerToken)
+ throws SecurityException {
final Context context = getContext();
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -480,7 +488,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
if (mExported && checkUser(pid, uid, context)) {
final String componentPerm = getReadPermission();
if (componentPerm != null) {
- if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) {
+ if (context.checkPermission(componentPerm, pid, uid, callerToken)
+ == PERMISSION_GRANTED) {
return;
} else {
missingPerm = componentPerm;
@@ -497,7 +506,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
for (PathPermission pp : pps) {
final String pathPerm = pp.getReadPermission();
if (pathPerm != null && pp.match(path)) {
- if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) {
+ if (context.checkPermission(pathPerm, pid, uid, callerToken)
+ == PERMISSION_GRANTED) {
return;
} else {
// any denied <path-permission> means we lose
@@ -515,8 +525,11 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
// last chance, check against any uri grants
- if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
- == PERMISSION_GRANTED) {
+ final int callingUserId = UserHandle.getUserId(uid);
+ final Uri userUri = (mSingleUser && !UserHandle.isSameUser(mMyUid, uid))
+ ? maybeAddUserId(uri, callingUserId) : uri;
+ if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ callerToken) == PERMISSION_GRANTED) {
return;
}
@@ -529,7 +542,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
/** {@hide} */
- protected void enforceWritePermissionInner(Uri uri) throws SecurityException {
+ protected void enforceWritePermissionInner(Uri uri, IBinder callerToken)
+ throws SecurityException {
final Context context = getContext();
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -542,7 +556,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
if (mExported && checkUser(pid, uid, context)) {
final String componentPerm = getWritePermission();
if (componentPerm != null) {
- if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) {
+ if (context.checkPermission(componentPerm, pid, uid, callerToken)
+ == PERMISSION_GRANTED) {
return;
} else {
missingPerm = componentPerm;
@@ -559,7 +574,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
for (PathPermission pp : pps) {
final String pathPerm = pp.getWritePermission();
if (pathPerm != null && pp.match(path)) {
- if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) {
+ if (context.checkPermission(pathPerm, pid, uid, callerToken)
+ == PERMISSION_GRANTED) {
return;
} else {
// any denied <path-permission> means we lose
@@ -577,8 +593,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
// last chance, check against any uri grants
- if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
- == PERMISSION_GRANTED) {
+ if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ callerToken) == PERMISSION_GRANTED) {
return;
}
@@ -1656,12 +1672,6 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
- /*
- * We may be using AsyncTask from binder threads. Make it init here
- * so its static handler is on the main thread.
- */
- AsyncTask.init();
-
mNoPerms = testing;
/*
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index cefc27f..e15ac94 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -288,7 +288,7 @@ public class ContentProviderClient {
remoteSignal = mContentProvider.createCancellationSignal();
signal.setRemote(remoteSignal);
}
- return mContentProvider.openFile(mPackageName, url, mode, remoteSignal);
+ return mContentProvider.openFile(mPackageName, url, mode, remoteSignal, null);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index 39286d6..f2e7fc4 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -234,9 +234,10 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
String mode = data.readString();
ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
data.readStrongBinder());
+ IBinder callerToken = data.readStrongBinder();
ParcelFileDescriptor fd;
- fd = openFile(callingPkg, url, mode, signal);
+ fd = openFile(callingPkg, url, mode, signal, callerToken);
reply.writeNoException();
if (fd != null) {
reply.writeInt(1);
@@ -575,7 +576,7 @@ final class ContentProviderProxy implements IContentProvider
@Override
public ParcelFileDescriptor openFile(
- String callingPkg, Uri url, String mode, ICancellationSignal signal)
+ String callingPkg, Uri url, String mode, ICancellationSignal signal, IBinder token)
throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -586,6 +587,7 @@ final class ContentProviderProxy implements IContentProvider
url.writeToParcel(data, 0);
data.writeString(mode);
data.writeStrongBinder(signal != null ? signal.asBinder() : null);
+ data.writeStrongBinder(token);
mRemote.transact(IContentProvider.OPEN_FILE_TRANSACTION, data, reply, 0);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index c9b7d0a..26735a6 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -37,6 +37,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.StatFs;
import android.os.UserHandle;
@@ -2141,6 +2142,7 @@ public abstract class Context {
MEDIA_SESSION_SERVICE,
BATTERY_SERVICE,
JOB_SCHEDULER_SERVICE,
+ MEDIA_PROJECTION_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ServiceName {}
@@ -2244,6 +2246,8 @@ public abstract class Context {
* @see android.media.MediaRouter
* @see #TELEPHONY_SERVICE
* @see android.telephony.TelephonyManager
+ * @see #TELEPHONY_SUBSCRIPTION_SERVICE
+ * @see android.telephony.SubscriptionManager
* @see #INPUT_METHOD_SERVICE
* @see android.view.inputmethod.InputMethodManager
* @see #UI_MODE_SERVICE
@@ -2588,6 +2592,16 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.telephony.SubscriptionManager} for handling management the
+ * telephony subscriptions of the device.
+ *
+ * @see #getSystemService
+ * @see android.telephony.SubscriptionManager
+ */
+ public static final String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.telecom.TelecomManager} to manage telecom-related features
* of the device.
*
@@ -2864,11 +2878,10 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a {@link
- * android.app.UsageStatsManager} for interacting with the status bar.
+ * android.app.usage.UsageStatsManager} for querying device usage stats.
*
* @see #getSystemService
- * @see android.app.UsageStatsManager
- * @hide
+ * @see android.app.usage.UsageStatsManager
*/
public static final String USAGE_STATS_SERVICE = "usagestats";
@@ -2921,6 +2934,11 @@ public abstract class Context {
@PackageManager.PermissionResult
public abstract int checkPermission(@NonNull String permission, int pid, int uid);
+ /** @hide */
+ @PackageManager.PermissionResult
+ public abstract int checkPermission(@NonNull String permission, int pid, int uid,
+ IBinder callerToken);
+
/**
* Determine whether the calling process of an IPC you are handling has been
* granted a particular permission. This is basically the same as calling
@@ -3108,6 +3126,10 @@ public abstract class Context {
public abstract int checkUriPermission(Uri uri, int pid, int uid,
@Intent.AccessUriMode int modeFlags);
+ /** @hide */
+ public abstract int checkUriPermission(Uri uri, int pid, int uid,
+ @Intent.AccessUriMode int modeFlags, IBinder callerToken);
+
/**
* Determine whether the calling process and user ID has been
* granted permission to access a specific URI. This is basically
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index ad7c350..cfae1cf 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -29,6 +29,7 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.UserHandle;
import android.view.DisplayAdjustments;
@@ -566,6 +567,12 @@ public class ContextWrapper extends Context {
return mBase.checkPermission(permission, pid, uid);
}
+ /** @hide */
+ @Override
+ public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
+ return mBase.checkPermission(permission, pid, uid, callerToken);
+ }
+
@Override
public int checkCallingPermission(String permission) {
return mBase.checkCallingPermission(permission);
@@ -608,6 +615,12 @@ public class ContextWrapper extends Context {
return mBase.checkUriPermission(uri, pid, uid, modeFlags);
}
+ /** @hide */
+ @Override
+ public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) {
+ return mBase.checkUriPermission(uri, pid, uid, modeFlags, callerToken);
+ }
+
@Override
public int checkCallingUriPermission(Uri uri, int modeFlags) {
return mBase.checkCallingUriPermission(uri, modeFlags);
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index f92a404..f858406 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -47,7 +47,8 @@ public interface IContentProvider extends IInterface {
public int update(String callingPkg, Uri url, ContentValues values, String selection,
String[] selectionArgs) throws RemoteException;
public ParcelFileDescriptor openFile(
- String callingPkg, Uri url, String mode, ICancellationSignal signal)
+ String callingPkg, Uri url, String mode, ICancellationSignal signal,
+ IBinder callerToken)
throws RemoteException, FileNotFoundException;
public AssetFileDescriptor openAssetFile(
String callingPkg, Uri url, String mode, ICancellationSignal signal)
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index af6f181..2fe727c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -41,6 +41,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Process;
import android.os.StrictMode;
import android.os.UserHandle;
import android.provider.DocumentsContract;
@@ -876,12 +877,44 @@ public class Intent implements Parcelable, Cloneable {
* related methods.
*/
public static Intent createChooser(Intent target, CharSequence title) {
+ return createChooser(target, title, null);
+ }
+
+ /**
+ * Convenience function for creating a {@link #ACTION_CHOOSER} Intent.
+ *
+ * <p>Builds a new {@link #ACTION_CHOOSER} Intent that wraps the given
+ * target intent, also optionally supplying a title. If the target
+ * intent has specified {@link #FLAG_GRANT_READ_URI_PERMISSION} or
+ * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, then these flags will also be
+ * set in the returned chooser intent, with its ClipData set appropriately:
+ * either a direct reflection of {@link #getClipData()} if that is non-null,
+ * or a new ClipData built from {@link #getData()}.</p>
+ *
+ * <p>The caller may optionally supply an {@link IntentSender} to receive a callback
+ * when the user makes a choice. This can be useful if the calling application wants
+ * to remember the last chosen target and surface it as a more prominent or one-touch
+ * affordance elsewhere in the UI for next time.</p>
+ *
+ * @param target The Intent that the user will be selecting an activity
+ * to perform.
+ * @param title Optional title that will be displayed in the chooser.
+ * @param sender Optional IntentSender to be called when a choice is made.
+ * @return Return a new Intent object that you can hand to
+ * {@link Context#startActivity(Intent) Context.startActivity()} and
+ * related methods.
+ */
+ public static Intent createChooser(Intent target, CharSequence title, IntentSender sender) {
Intent intent = new Intent(ACTION_CHOOSER);
intent.putExtra(EXTRA_INTENT, target);
if (title != null) {
intent.putExtra(EXTRA_TITLE, title);
}
+ if (sender != null) {
+ intent.putExtra(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER, sender);
+ }
+
// Migrate any clip data and flags from target.
int permFlags = target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION
| FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
@@ -1368,14 +1401,36 @@ public class Intent implements Parcelable, Cloneable {
= "android.intent.extra.ORIGINATING_URI";
/**
- * Used as a URI extra field with {@link #ACTION_INSTALL_PACKAGE} and
- * {@link #ACTION_VIEW} to indicate the HTTP referrer URI associated with the Intent
- * data field or {@link #EXTRA_ORIGINATING_URI}.
+ * This extra can be used with any Intent used to launch an activity, supplying information
+ * about who is launching that activity. This field contains a {@link android.net.Uri}
+ * object, typically an http: or https: URI of the web site that the referral came from;
+ * it can also use the {@link #URI_ANDROID_APP_SCHEME android-app:} scheme to identify
+ * a native application that it came from.
+ *
+ * <p>To retrieve this value in a client, use {@link android.app.Activity#getReferrer}
+ * instead of directly retrieving the extra. It is also valid for applications to
+ * instead supply {@link #EXTRA_REFERRER_NAME} for cases where they can only create
+ * a string, not a Uri; the field here, if supplied, will always take precedence,
+ * however.</p>
+ *
+ * @see #EXTRA_REFERRER_NAME
*/
public static final String EXTRA_REFERRER
= "android.intent.extra.REFERRER";
/**
+ * Alternate version of {@link #EXTRA_REFERRER} that supplies the URI as a String rather
+ * than a {@link android.net.Uri} object. Only for use in cases where Uri objects can
+ * not be created, in particular when Intent extras are supplied through the
+ * {@link #URI_INTENT_SCHEME intent:} or {@link #URI_ANDROID_APP_SCHEME android-app:}
+ * schemes.
+ *
+ * @see #EXTRA_REFERRER
+ */
+ public static final String EXTRA_REFERRER_NAME
+ = "android.intent.extra.REFERRER_NAME";
+
+ /**
* Used as an int extra field with {@link #ACTION_INSTALL_PACKAGE} and
* {@link} #ACTION_VIEW} to indicate the uid of the package that initiated the install
* @hide
@@ -3140,6 +3195,26 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.extra.REPLACEMENT_EXTRAS";
/**
+ * An {@link IntentSender} that will be notified if a user successfully chooses a target
+ * component to handle an action in an {@link #ACTION_CHOOSER} activity. The IntentSender
+ * will have the extra {@link #EXTRA_CHOSEN_COMPONENT} appended to it containing the
+ * {@link ComponentName} of the chosen component.
+ *
+ * <p>In some situations this callback may never come, for example if the user abandons
+ * the chooser, switches to another task or any number of other reasons. Apps should not
+ * be written assuming that this callback will always occur.</p>
+ */
+ public static final String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER =
+ "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
+
+ /**
+ * The {@link ComponentName} chosen by the user to complete an action.
+ *
+ * @see #EXTRA_CHOSEN_COMPONENT_INTENT_SENDER
+ */
+ public static final String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
+
+ /**
* A {@link android.view.KeyEvent} object containing the event that
* triggered the creation of the Intent it is in.
*/
@@ -3535,6 +3610,10 @@ public class Intent implements Parcelable, Cloneable {
* the user navigates away from it, the activity is finished. This may also
* be set with the {@link android.R.styleable#AndroidManifestActivity_noHistory
* noHistory} attribute.
+ *
+ * <p>If set, {@link android.app.Activity#onActivityResult onActivityResult()}
+ * is never invoked when the current activity starts a new activity which
+ * sets a result and finishes.
*/
public static final int FLAG_ACTIVITY_NO_HISTORY = 0x40000000;
/**
@@ -3688,7 +3767,7 @@ public class Intent implements Parcelable, Cloneable {
* 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.
+ * containing different documents will appear in the recent tasks list.
*
* <p>The use of the activity attribute form of this,
* {@link android.R.attr#documentLaunchMode}, is
@@ -3866,6 +3945,91 @@ public class Intent implements Parcelable, Cloneable {
*/
public static final int URI_INTENT_SCHEME = 1<<0;
+ /**
+ * Flag for use with {@link #toUri} and {@link #parseUri}: the URI string
+ * always has the "android-app:" scheme. This is a variation of
+ * {@link #URI_INTENT_SCHEME} whose format is simpler for the case of an
+ * http/https URI being delivered to a specific package name. The format
+ * is:
+ *
+ * <pre class="prettyprint">
+ * android-app://{package_id}[/{scheme}[/{host}[/{path}]]][#Intent;{...}]</pre>
+ *
+ * <p>In this scheme, only the <code>package_id</code> is required. If you include a host,
+ * you must also include a scheme; including a path also requires both a host and a scheme.
+ * The final #Intent; fragment can be used without a scheme, host, or path.
+ * Note that this can not be
+ * used with intents that have a {@link #setSelector}, since the base intent
+ * will always have an explicit package name.</p>
+ *
+ * <p>Some examples of how this scheme maps to Intent objects:</p>
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ * <colgroup align="left" />
+ * <colgroup align="left" />
+ * <thead>
+ * <tr><th>URI</th> <th>Intent</th></tr>
+ * </thead>
+ *
+ * <tbody>
+ * <tr><td><code>android-app://com.example.app</code></td>
+ * <td><table style="margin:0;border:0;cellpadding:0;cellspacing:0">
+ * <tr><td>Action: </td><td>{@link #ACTION_MAIN}</td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * </table></td>
+ * </tr>
+ * <tr><td><code>android-app://com.example.app/http/example.com</code></td>
+ * <td><table style="margin:0;border:0;cellpadding:0;cellspacing:0">
+ * <tr><td>Action: </td><td>{@link #ACTION_VIEW}</td></tr>
+ * <tr><td>Data: </td><td><code>http://example.com/</code></td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * </table></td>
+ * </tr>
+ * <tr><td><code>android-app://com.example.app/http/example.com/foo?1234</code></td>
+ * <td><table style="margin:0;border:0;cellpadding:0;cellspacing:0">
+ * <tr><td>Action: </td><td>{@link #ACTION_VIEW}</td></tr>
+ * <tr><td>Data: </td><td><code>http://example.com/foo?1234</code></td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * </table></td>
+ * </tr>
+ * <tr><td><code>android-app://com.example.app/<br />#Intent;action=com.example.MY_ACTION;end</code></td>
+ * <td><table style="margin:0;border:0;cellpadding:0;cellspacing:0">
+ * <tr><td>Action: </td><td><code>com.example.MY_ACTION</code></td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * </table></td>
+ * </tr>
+ * <tr><td><code>android-app://com.example.app/http/example.com/foo?1234<br />#Intent;action=com.example.MY_ACTION;end</code></td>
+ * <td><table style="margin:0;border:0;cellpadding:0;cellspacing:0">
+ * <tr><td>Action: </td><td><code>com.example.MY_ACTION</code></td></tr>
+ * <tr><td>Data: </td><td><code>http://example.com/foo?1234</code></td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * </table></td>
+ * </tr>
+ * <tr><td><code>android-app://com.example.app/<br />#Intent;action=com.example.MY_ACTION;<br />i.some_int=100;S.some_str=hello;end</code></td>
+ * <td><table border="" style="margin:0" >
+ * <tr><td>Action: </td><td><code>com.example.MY_ACTION</code></td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * <tr><td>Extras: </td><td><code>some_int=(int)100<br />some_str=(String)hello</code></td></tr>
+ * </table></td>
+ * </tr>
+ * </tbody>
+ * </table>
+ */
+ public static final int URI_ANDROID_APP_SCHEME = 1<<1;
+
+ /**
+ * Flag for use with {@link #toUri} and {@link #parseUri}: allow parsing
+ * of unsafe information. In particular, the flags {@link #FLAG_GRANT_READ_URI_PERMISSION},
+ * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, {@link #FLAG_GRANT_PERSISTABLE_URI_PERMISSION},
+ * and {@link #FLAG_GRANT_PREFIX_URI_PERMISSION} flags can not be set, so that the
+ * generated Intent can not cause unexpected data access to happen.
+ *
+ * <p>If you do not trust the source of the URI being parsed, you should still do further
+ * processing to protect yourself from it. In particular, when using it to start an
+ * activity you should usually add in {@link #CATEGORY_BROWSABLE} to limit the activities
+ * that can handle it.</p>
+ */
+ public static final int URI_ALLOW_UNSAFE = 1<<2;
+
// ---------------------------------------------------------------------
private String mAction;
@@ -4126,8 +4290,8 @@ public class Intent implements Parcelable, Cloneable {
* the scheme and full path.
*
* @param uri The URI to turn into an Intent.
- * @param flags Additional processing flags. Either 0 or
- * {@link #URI_INTENT_SCHEME}.
+ * @param flags Additional processing flags. Either 0,
+ * {@link #URI_INTENT_SCHEME}, or {@link #URI_ANDROID_APP_SCHEME}.
*
* @return Intent The newly created Intent object.
*
@@ -4140,9 +4304,11 @@ public class Intent implements Parcelable, Cloneable {
public static Intent parseUri(String uri, int flags) throws URISyntaxException {
int i = 0;
try {
- // Validate intent scheme for if requested.
- if ((flags&URI_INTENT_SCHEME) != 0) {
- if (!uri.startsWith("intent:")) {
+ final boolean androidApp = uri.startsWith("android-app:");
+
+ // Validate intent scheme if requested.
+ if ((flags&(URI_INTENT_SCHEME|URI_ANDROID_APP_SCHEME)) != 0) {
+ if (!uri.startsWith("intent:") && !androidApp) {
Intent intent = new Intent(ACTION_VIEW);
try {
intent.setData(Uri.parse(uri));
@@ -4153,24 +4319,40 @@ public class Intent implements Parcelable, Cloneable {
}
}
- // simple case
i = uri.lastIndexOf("#");
- if (i == -1) return new Intent(ACTION_VIEW, Uri.parse(uri));
+ // simple case
+ if (i == -1) {
+ if (!androidApp) {
+ return new Intent(ACTION_VIEW, Uri.parse(uri));
+ }
// old format Intent URI
- if (!uri.startsWith("#Intent;", i)) return getIntentOld(uri);
+ } else if (!uri.startsWith("#Intent;", i)) {
+ if (!androidApp) {
+ return getIntentOld(uri, flags);
+ } else {
+ i = -1;
+ }
+ }
// new format
Intent intent = new Intent(ACTION_VIEW);
Intent baseIntent = intent;
+ boolean explicitAction = false;
+ boolean inSelector = false;
// fetch data part, if present
- String data = i >= 0 ? uri.substring(0, i) : null;
String scheme = null;
- i += "#Intent;".length();
+ String data;
+ if (i >= 0) {
+ data = uri.substring(0, i);
+ i += 8; // length of "#Intent;"
+ } else {
+ data = uri;
+ }
// loop over contents of Intent, all name=value;
- while (!uri.startsWith("end", i)) {
+ while (i >= 0 && !uri.startsWith("end", i)) {
int eq = uri.indexOf('=', i);
if (eq < 0) eq = i-1;
int semi = uri.indexOf(';', i);
@@ -4179,6 +4361,9 @@ public class Intent implements Parcelable, Cloneable {
// action
if (uri.startsWith("action=", i)) {
intent.setAction(value);
+ if (!inSelector) {
+ explicitAction = true;
+ }
}
// categories
@@ -4194,6 +4379,9 @@ public class Intent implements Parcelable, Cloneable {
// launch flags
else if (uri.startsWith("launchFlags=", i)) {
intent.mFlags = Integer.decode(value).intValue();
+ if ((flags& URI_ALLOW_UNSAFE) == 0) {
+ intent.mFlags &= ~IMMUTABLE_FLAGS;
+ }
}
// package
@@ -4208,7 +4396,11 @@ public class Intent implements Parcelable, Cloneable {
// scheme
else if (uri.startsWith("scheme=", i)) {
- scheme = value;
+ if (inSelector) {
+ intent.mData = Uri.parse(value + ":");
+ } else {
+ scheme = value;
+ }
}
// source bounds
@@ -4219,6 +4411,7 @@ public class Intent implements Parcelable, Cloneable {
// selector
else if (semi == (i+3) && uri.startsWith("SEL", i)) {
intent = new Intent();
+ inSelector = true;
}
// extra
@@ -4244,9 +4437,11 @@ public class Intent implements Parcelable, Cloneable {
i = semi + 1;
}
- if (intent != baseIntent) {
+ if (inSelector) {
// The Intent had a selector; fix it up.
- baseIntent.setSelector(intent);
+ if (baseIntent.mPackage == null) {
+ baseIntent.setSelector(intent);
+ }
intent = baseIntent;
}
@@ -4256,6 +4451,52 @@ public class Intent implements Parcelable, Cloneable {
if (scheme != null) {
data = scheme + ':' + data;
}
+ } else if (data.startsWith("android-app:")) {
+ if (data.charAt(12) == '/' && data.charAt(13) == '/') {
+ // Correctly formed android-app, first part is package name.
+ int end = data.indexOf('/', 14);
+ if (end < 0) {
+ // All we have is a package name.
+ intent.mPackage = data.substring(14);
+ if (!explicitAction) {
+ intent.setAction(ACTION_MAIN);
+ }
+ data = "";
+ } else {
+ // Target the Intent at the given package name always.
+ String authority = null;
+ intent.mPackage = data.substring(14, end);
+ int newEnd;
+ if ((end+1) < data.length()) {
+ if ((newEnd=data.indexOf('/', end+1)) >= 0) {
+ // Found a scheme, remember it.
+ scheme = data.substring(end+1, newEnd);
+ end = newEnd;
+ if (end < data.length() && (newEnd=data.indexOf('/', end+1)) >= 0) {
+ // Found a authority, remember it.
+ authority = data.substring(end+1, newEnd);
+ end = newEnd;
+ }
+ } else {
+ // All we have is a scheme.
+ scheme = data.substring(end+1);
+ }
+ }
+ if (scheme == null) {
+ // If there was no scheme, then this just targets the package.
+ if (!explicitAction) {
+ intent.setAction(ACTION_MAIN);
+ }
+ data = "";
+ } else if (authority == null) {
+ data = scheme + ":";
+ } else {
+ data = scheme + "://" + authority + data.substring(end);
+ }
+ }
+ } else {
+ data = "";
+ }
}
if (data.length() > 0) {
@@ -4275,6 +4516,10 @@ public class Intent implements Parcelable, Cloneable {
}
public static Intent getIntentOld(String uri) throws URISyntaxException {
+ return getIntentOld(uri, 0);
+ }
+
+ private static Intent getIntentOld(String uri, int flags) throws URISyntaxException {
Intent intent;
int i = uri.lastIndexOf('#');
@@ -4323,6 +4568,9 @@ public class Intent implements Parcelable, Cloneable {
i += 12;
int j = uri.indexOf(')', i);
intent.mFlags = Integer.decode(uri.substring(i, j)).intValue();
+ if ((flags& URI_ALLOW_UNSAFE) == 0) {
+ intent.mFlags &= ~IMMUTABLE_FLAGS;
+ }
i = j + 1;
}
@@ -7031,14 +7279,53 @@ public class Intent implements Parcelable, Cloneable {
* <p>You can convert the returned string back to an Intent with
* {@link #getIntent}.
*
- * @param flags Additional operating flags. Either 0 or
- * {@link #URI_INTENT_SCHEME}.
+ * @param flags Additional operating flags. Either 0,
+ * {@link #URI_INTENT_SCHEME}, or {@link #URI_ANDROID_APP_SCHEME}.
*
* @return Returns a URI encoding URI string describing the entire contents
* of the Intent.
*/
public String toUri(int flags) {
StringBuilder uri = new StringBuilder(128);
+ if ((flags&URI_ANDROID_APP_SCHEME) != 0) {
+ if (mPackage == null) {
+ throw new IllegalArgumentException(
+ "Intent must include an explicit package name to build an android-app: "
+ + this);
+ }
+ uri.append("android-app://");
+ uri.append(mPackage);
+ String scheme = null;
+ if (mData != null) {
+ scheme = mData.getScheme();
+ if (scheme != null) {
+ uri.append('/');
+ uri.append(scheme);
+ String authority = mData.getEncodedAuthority();
+ if (authority != null) {
+ uri.append('/');
+ uri.append(authority);
+ String path = mData.getEncodedPath();
+ if (path != null) {
+ uri.append(path);
+ }
+ String queryParams = mData.getEncodedQuery();
+ if (queryParams != null) {
+ uri.append('?');
+ uri.append(queryParams);
+ }
+ String fragment = mData.getEncodedFragment();
+ if (fragment != null) {
+ uri.append('#');
+ uri.append(fragment);
+ }
+ }
+ }
+ }
+ toUriFragment(uri, null, scheme == null ? Intent.ACTION_MAIN : Intent.ACTION_VIEW,
+ mPackage, flags);
+ return uri.toString();
+ }
String scheme = null;
if (mData != null) {
String data = mData.toString();
@@ -7068,27 +7355,38 @@ public class Intent implements Parcelable, Cloneable {
uri.append("intent:");
}
- uri.append("#Intent;");
+ toUriFragment(uri, scheme, Intent.ACTION_VIEW, null, flags);
- toUriInner(uri, scheme, flags);
+ return uri.toString();
+ }
+
+ private void toUriFragment(StringBuilder uri, String scheme, String defAction,
+ String defPackage, int flags) {
+ StringBuilder frag = new StringBuilder(128);
+
+ toUriInner(frag, scheme, defAction, defPackage, flags);
if (mSelector != null) {
- uri.append("SEL;");
+ frag.append("SEL;");
// Note that for now we are not going to try to handle the
// data part; not clear how to represent this as a URI, and
// not much utility in it.
- mSelector.toUriInner(uri, null, flags);
+ mSelector.toUriInner(frag, mSelector.mData != null ? mSelector.mData.getScheme() : null,
+ null, null, flags);
}
- uri.append("end");
-
- return uri.toString();
+ if (frag.length() > 0) {
+ uri.append("#Intent;");
+ uri.append(frag);
+ uri.append("end");
+ }
}
- private void toUriInner(StringBuilder uri, String scheme, int flags) {
+ private void toUriInner(StringBuilder uri, String scheme, String defAction,
+ String defPackage, int flags) {
if (scheme != null) {
uri.append("scheme=").append(scheme).append(';');
}
- if (mAction != null) {
+ if (mAction != null && !mAction.equals(defAction)) {
uri.append("action=").append(Uri.encode(mAction)).append(';');
}
if (mCategories != null) {
@@ -7102,7 +7400,7 @@ public class Intent implements Parcelable, Cloneable {
if (mFlags != 0) {
uri.append("launchFlags=0x").append(Integer.toHexString(mFlags)).append(';');
}
- if (mPackage != null) {
+ if (mPackage != null && !mPackage.equals(defPackage)) {
uri.append("package=").append(Uri.encode(mPackage)).append(';');
}
if (mComponent != null) {
@@ -7446,8 +7744,10 @@ public class Intent implements Parcelable, Cloneable {
*/
public void prepareToEnterProcess() {
if (mContentUserHint != UserHandle.USER_CURRENT) {
- fixUris(mContentUserHint);
- mContentUserHint = UserHandle.USER_CURRENT;
+ if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) {
+ fixUris(mContentUserHint);
+ mContentUserHint = UserHandle.USER_CURRENT;
+ }
}
}
diff --git a/core/java/android/content/UriMatcher.java b/core/java/android/content/UriMatcher.java
index 8487dae..71a035e 100644
--- a/core/java/android/content/UriMatcher.java
+++ b/core/java/android/content/UriMatcher.java
@@ -20,7 +20,6 @@ import android.net.Uri;
import java.util.ArrayList;
import java.util.List;
-import java.util.regex.Pattern;
/**
Utility class to aid in matching URIs in content providers.
@@ -171,7 +170,7 @@ public class UriMatcher
if (path.length() > 0 && path.charAt(0) == '/') {
newPath = path.substring(1);
}
- tokens = PATH_SPLIT_PATTERN.split(newPath);
+ tokens = newPath.split("/");
}
int numTokens = tokens != null ? tokens.length : 0;
@@ -207,8 +206,6 @@ public class UriMatcher
node.mCode = code;
}
- static final Pattern PATH_SPLIT_PATTERN = Pattern.compile("/");
-
/**
* Try to match against the path in a url.
*
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index e822708..e1a2aa9 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -881,6 +881,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
/**
* @hide
*/
+ public boolean isForwardLocked() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0;
+ }
+
+ /**
+ * @hide
+ */
@Override protected ApplicationInfo getApplicationInfo() {
return this;
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index b37713a..b518498 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -438,6 +438,7 @@ interface IPackageManager {
boolean isFirstBoot();
boolean isOnlyCoreApps();
+ boolean isUpgrade();
void setPermissionEnforced(String permission, boolean enforced);
boolean isPermissionEnforced(String permission);
diff --git a/core/java/android/content/pm/LabeledIntent.aidl b/core/java/android/content/pm/LabeledIntent.aidl
new file mode 100644
index 0000000..ad96759
--- /dev/null
+++ b/core/java/android/content/pm/LabeledIntent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+parcelable LabeledIntent;
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 5ee0b67..c164340 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -499,20 +499,4 @@ public class LauncherApps {
obtainMessage(MSG_UNAVAILABLE, info).sendToTarget();
}
}
-
- /**
- * TODO Remove after 2014-09-22
- * @hide
- */
- public void addCallback(Callback callback) {
- registerCallback(callback);
- }
-
- /**
- * TODO Remove after 2014-09-22
- * @hide
- */
- public void removeCallback(Callback callback) {
- unregisterCallback(callback);
- }
}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index b66bd01..9223269 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -41,14 +41,30 @@ public class PackageInfo implements Parcelable {
* attribute.
*/
public int versionCode;
-
+
/**
* The version name of this package, as specified by the &lt;manifest&gt;
* tag's {@link android.R.styleable#AndroidManifest_versionName versionName}
* attribute.
*/
public String versionName;
-
+
+ /**
+ * The revision number of the base APK for this package, as specified by the
+ * &lt;manifest&gt; tag's
+ * {@link android.R.styleable#AndroidManifest_revisionCode revisionCode}
+ * attribute.
+ */
+ public int baseRevisionCode;
+
+ /**
+ * The revision number of any split APKs for this package, as specified by
+ * the &lt;manifest&gt; tag's
+ * {@link android.R.styleable#AndroidManifest_revisionCode revisionCode}
+ * attribute. Indexes are a 1:1 mapping against {@link #splitNames}.
+ */
+ public int[] splitRevisionCodes;
+
/**
* The shared user ID name of this package, as specified by the &lt;manifest&gt;
* tag's {@link android.R.styleable#AndroidManifest_sharedUserId sharedUserId}
@@ -257,21 +273,26 @@ public class PackageInfo implements Parcelable {
public PackageInfo() {
}
+ @Override
public String toString() {
return "PackageInfo{"
+ Integer.toHexString(System.identityHashCode(this))
+ " " + packageName + "}";
}
+ @Override
public int describeContents() {
return 0;
}
+ @Override
public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeString(packageName);
dest.writeStringArray(splitNames);
dest.writeInt(versionCode);
dest.writeString(versionName);
+ dest.writeInt(baseRevisionCode);
+ dest.writeIntArray(splitRevisionCodes);
dest.writeString(sharedUserId);
dest.writeInt(sharedUserLabel);
if (applicationInfo != null) {
@@ -305,10 +326,12 @@ public class PackageInfo implements Parcelable {
public static final Parcelable.Creator<PackageInfo> CREATOR
= new Parcelable.Creator<PackageInfo>() {
+ @Override
public PackageInfo createFromParcel(Parcel source) {
return new PackageInfo(source);
}
+ @Override
public PackageInfo[] newArray(int size) {
return new PackageInfo[size];
}
@@ -316,9 +339,11 @@ public class PackageInfo implements Parcelable {
private PackageInfo(Parcel source) {
packageName = source.readString();
- splitNames = source.readStringArray();
+ splitNames = source.createStringArray();
versionCode = source.readInt();
versionName = source.readString();
+ baseRevisionCode = source.readInt();
+ splitRevisionCodes = source.createIntArray();
sharedUserId = source.readString();
sharedUserLabel = source.readInt();
int hasApp = source.readInt();
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index e336c5f..1efe082 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -19,6 +19,8 @@ package android.content.pm;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.content.PackageHelper;
+
/**
* Basic information about a package as specified in its manifest.
* Utility class used in PackageManager methods
@@ -31,11 +33,19 @@ public class PackageInfoLite implements Parcelable {
*/
public String packageName;
+ /** Names of any split APKs, ordered by parsed splitName */
+ public String[] splitNames;
+
/**
* The android:versionCode of the package.
*/
public int versionCode;
+ /** Revision code of base APK */
+ public int baseRevisionCode;
+ /** Revision codes of any split APKs, ordered by parsed splitName */
+ public int[] splitRevisionCodes;
+
/**
* The android:multiArch flag from the package manifest. If set,
* we will extract all native libraries for the given app, not just those
@@ -70,7 +80,10 @@ public class PackageInfoLite implements Parcelable {
public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeString(packageName);
+ dest.writeStringArray(splitNames);
dest.writeInt(versionCode);
+ dest.writeInt(baseRevisionCode);
+ dest.writeIntArray(splitRevisionCodes);
dest.writeInt(recommendedInstallLocation);
dest.writeInt(installLocation);
dest.writeInt(multiArch ? 1 : 0);
@@ -96,7 +109,10 @@ public class PackageInfoLite implements Parcelable {
private PackageInfoLite(Parcel source) {
packageName = source.readString();
+ splitNames = source.createStringArray();
versionCode = source.readInt();
+ baseRevisionCode = source.readInt();
+ splitRevisionCodes = source.createIntArray();
recommendedInstallLocation = source.readInt();
installLocation = source.readInt();
multiArch = (source.readInt() != 0);
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index cacdf8e..22a899c 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -138,7 +138,7 @@ public class PackageItemInfo {
}
return packageName;
}
-
+
/**
* Retrieve the current graphical icon associated with this item. This
* will call back on the given PackageManager to load the icon from
@@ -156,6 +156,23 @@ public class PackageItemInfo {
}
/**
+ * Retrieve the current graphical icon associated with this item without
+ * the addition of a work badge if applicable.
+ * This will call back on the given PackageManager to load the icon from
+ * the application.
+ *
+ * @param pm A PackageManager from which the icon can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a Drawable containing the item's icon. If the
+ * item does not have an icon, the item's default icon is returned
+ * such as the default activity icon.
+ */
+ public Drawable loadUnbadgedIcon(PackageManager pm) {
+ return pm.loadUnbadgedItemIcon(this, getApplicationInfo());
+ }
+
+ /**
* Retrieve the current graphical banner associated with this item. This
* will call back on the given PackageManager to load the banner from
* the application.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index e519163..e9f7c50 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3867,6 +3867,13 @@ public abstract class PackageManager {
public abstract VerifierDeviceIdentity getVerifierDeviceIdentity();
/**
+ * Returns true if the device is upgrading, such as first boot after OTA.
+ *
+ * @hide
+ */
+ public abstract boolean isUpgrade();
+
+ /**
* Return interface that offers the ability to install, upgrade, and remove
* applications on the device.
*/
@@ -3911,6 +3918,11 @@ public abstract class PackageManager {
*/
public abstract Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo);
+ /**
+ * @hide
+ */
+ public abstract Drawable loadUnbadgedItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo);
+
/** {@hide} */
public abstract boolean isPackageAvailable(String packageName);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 131ca39..4d9445d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -74,7 +74,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -262,11 +261,16 @@ public class PackageParser {
/** Paths of any split APKs, ordered by parsed splitName */
public final String[] splitCodePaths;
+ /** Revision code of base APK */
+ public final int baseRevisionCode;
+ /** Revision codes of any split APKs, ordered by parsed splitName */
+ public final int[] splitRevisionCodes;
+
public final boolean coreApp;
public final boolean multiArch;
public PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
- String[] splitCodePaths) {
+ String[] splitCodePaths, int[] splitRevisionCodes) {
this.packageName = baseApk.packageName;
this.versionCode = baseApk.versionCode;
this.installLocation = baseApk.installLocation;
@@ -275,6 +279,8 @@ public class PackageParser {
this.codePath = codePath;
this.baseCodePath = baseApk.codePath;
this.splitCodePaths = splitCodePaths;
+ this.baseRevisionCode = baseApk.revisionCode;
+ this.splitRevisionCodes = splitRevisionCodes;
this.coreApp = baseApk.coreApp;
this.multiArch = baseApk.multiArch;
}
@@ -297,6 +303,7 @@ public class PackageParser {
public final String packageName;
public final String splitName;
public final int versionCode;
+ public final int revisionCode;
public final int installLocation;
public final VerifierInfo[] verifiers;
public final Signature[] signatures;
@@ -304,12 +311,13 @@ public class PackageParser {
public final boolean multiArch;
public ApkLite(String codePath, String packageName, String splitName, int versionCode,
- int installLocation, List<VerifierInfo> verifiers, Signature[] signatures,
- boolean coreApp, boolean multiArch) {
+ int revisionCode, int installLocation, List<VerifierInfo> verifiers,
+ Signature[] signatures, boolean coreApp, boolean multiArch) {
this.codePath = codePath;
this.packageName = packageName;
this.splitName = splitName;
this.versionCode = versionCode;
+ this.revisionCode = revisionCode;
this.installLocation = installLocation;
this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]);
this.signatures = signatures;
@@ -380,7 +388,7 @@ public class PackageParser {
*/
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
- HashSet<String> grantedPermissions, PackageUserState state) {
+ ArraySet<String> grantedPermissions, PackageUserState state) {
return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
grantedPermissions, state, UserHandle.getCallingUserId());
@@ -401,7 +409,7 @@ public class PackageParser {
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
- HashSet<String> grantedPermissions, PackageUserState state, int userId) {
+ ArraySet<String> grantedPermissions, PackageUserState state, int userId) {
if (!checkUseInstalledOrHidden(flags, state)) {
return null;
@@ -410,6 +418,8 @@ public class PackageParser {
pi.packageName = p.packageName;
pi.splitNames = p.splitNames;
pi.versionCode = p.mVersionCode;
+ pi.baseRevisionCode = p.baseRevisionCode;
+ pi.splitRevisionCodes = p.splitRevisionCodes;
pi.versionName = p.mVersionName;
pi.sharedUserId = p.mSharedUserId;
pi.sharedUserLabel = p.mSharedUserLabel;
@@ -648,7 +658,7 @@ public class PackageParser {
throws PackageParserException {
final ApkLite baseApk = parseApkLite(packageFile, flags);
final String packagePath = packageFile.getAbsolutePath();
- return new PackageLite(packagePath, baseApk, null, null);
+ return new PackageLite(packagePath, baseApk, null, null, null);
}
private static PackageLite parseClusterPackageLite(File packageDir, int flags)
@@ -705,20 +715,24 @@ public class PackageParser {
String[] splitNames = null;
String[] splitCodePaths = null;
+ int[] splitRevisionCodes = null;
if (size > 0) {
splitNames = new String[size];
splitCodePaths = new String[size];
+ splitRevisionCodes = new int[size];
splitNames = apks.keySet().toArray(splitNames);
Arrays.sort(splitNames, sSplitNameComparator);
for (int i = 0; i < size; i++) {
splitCodePaths[i] = apks.get(splitNames[i]).codePath;
+ splitRevisionCodes[i] = apks.get(splitNames[i]).revisionCode;
}
}
final String codePath = packageDir.getAbsolutePath();
- return new PackageLite(codePath, baseApk, splitNames, splitCodePaths);
+ return new PackageLite(codePath, baseApk, splitNames, splitCodePaths,
+ splitRevisionCodes);
}
/**
@@ -783,6 +797,7 @@ public class PackageParser {
final int num = lite.splitNames.length;
pkg.splitNames = lite.splitNames;
pkg.splitCodePaths = lite.splitCodePaths;
+ pkg.splitRevisionCodes = lite.splitRevisionCodes;
pkg.splitFlags = new int[num];
pkg.splitPrivateFlags = new int[num];
@@ -1251,25 +1266,21 @@ public class PackageParser {
int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
int versionCode = 0;
+ int revisionCode = 0;
boolean coreApp = false;
boolean multiArch = false;
- int numFound = 0;
for (int i = 0; i < attrs.getAttributeCount(); i++) {
- String attr = attrs.getAttributeName(i);
+ final String attr = attrs.getAttributeName(i);
if (attr.equals("installLocation")) {
installLocation = attrs.getAttributeIntValue(i,
PARSE_DEFAULT_INSTALL_LOCATION);
- numFound++;
} else if (attr.equals("versionCode")) {
versionCode = attrs.getAttributeIntValue(i, 0);
- numFound++;
+ } else if (attr.equals("revisionCode")) {
+ revisionCode = attrs.getAttributeIntValue(i, 0);
} else if (attr.equals("coreApp")) {
coreApp = attrs.getAttributeBooleanValue(i, false);
- numFound++;
- }
- if (numFound >= 3) {
- break;
}
}
@@ -1303,7 +1314,7 @@ public class PackageParser {
}
return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
- installLocation, verifiers, signatures, coreApp, multiArch);
+ revisionCode, installLocation, verifiers, signatures, coreApp, multiArch);
}
/**
@@ -1361,6 +1372,8 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifest);
pkg.mVersionCode = pkg.applicationInfo.versionCode = sa.getInteger(
com.android.internal.R.styleable.AndroidManifest_versionCode, 0);
+ pkg.baseRevisionCode = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifest_revisionCode, 0);
pkg.mVersionName = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifest_versionName, 0);
if (pkg.mVersionName != null) {
@@ -3518,6 +3531,10 @@ public class PackageParser {
outError[0] = "<provider> does not include authorities attribute";
return null;
}
+ if (cpname.length() <= 0) {
+ outError[0] = "<provider> has empty authorities attribute";
+ return null;
+ }
p.info.authority = cpname.intern();
if (!parseProviderTags(res, parser, attrs, p, outError)) {
@@ -4173,6 +4190,7 @@ public class PackageParser {
public final static class Package {
public String packageName;
+
/** Names of any split APKs, ordered by parsed splitName */
public String[] splitNames;
@@ -4190,6 +4208,11 @@ public class PackageParser {
/** Paths of any split APKs, ordered by parsed splitName */
public String[] splitCodePaths;
+ /** Revision code of base APK */
+ public int baseRevisionCode;
+ /** Revision codes of any split APKs, ordered by parsed splitName */
+ public int[] splitRevisionCodes;
+
/** Flags of any split APKs; ordered by parsed splitName */
public int[] splitFlags;
@@ -4234,7 +4257,7 @@ public class PackageParser {
// The version code declared for this package.
public int mVersionCode;
-
+
// The version name declared for this package.
public String mVersionName;
@@ -4408,6 +4431,13 @@ public class PackageParser {
return false;
}
+ /**
+ * @hide
+ */
+ public boolean isForwardLocked() {
+ return applicationInfo.isForwardLocked();
+ }
+
public String toString() {
return "Package{"
+ Integer.toHexString(System.identityHashCode(this))
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 4dcad6f..a9c7be3 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -18,7 +18,7 @@ package android.content.pm;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
-import java.util.HashSet;
+import android.util.ArraySet;
/**
* Per-user state information about a package.
@@ -34,8 +34,8 @@ public class PackageUserState {
public String lastDisableAppCaller;
- public HashSet<String> disabledComponents;
- public HashSet<String> enabledComponents;
+ public ArraySet<String> disabledComponents;
+ public ArraySet<String> enabledComponents;
public PackageUserState() {
installed = true;
@@ -51,9 +51,9 @@ public class PackageUserState {
hidden = o.hidden;
lastDisableAppCaller = o.lastDisableAppCaller;
disabledComponents = o.disabledComponents != null
- ? new HashSet<String>(o.disabledComponents) : null;
+ ? new ArraySet<String>(o.disabledComponents) : null;
enabledComponents = o.enabledComponents != null
- ? new HashSet<String>(o.enabledComponents) : null;
+ ? new ArraySet<String>(o.enabledComponents) : null;
blockUninstall = o.blockUninstall;
}
-} \ No newline at end of file
+}
diff --git a/core/java/android/content/pm/ParceledListSlice.java b/core/java/android/content/pm/ParceledListSlice.java
index 8a43472..335a45e 100644
--- a/core/java/android/content/pm/ParceledListSlice.java
+++ b/core/java/android/content/pm/ParceledListSlice.java
@@ -30,6 +30,12 @@ import java.util.List;
* Transfer a large list of Parcelable objects across an IPC. Splits into
* multiple transactions if needed.
*
+ * Caveat: for efficiency and security, all elements must be the same concrete type.
+ * In order to avoid writing the class name of each object, we must ensure that
+ * each object is the same type, or else unparceling then reparceling the data may yield
+ * a different result if the class name encoded in the Parcelable is a Base type.
+ * See b/17671747.
+ *
* @hide
*/
public class ParceledListSlice<T extends Parcelable> implements Parcelable {
@@ -56,13 +62,25 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable {
if (N <= 0) {
return;
}
+
Parcelable.Creator<T> creator = p.readParcelableCreator(loader);
+ Class<?> listElementClass = null;
+
int i = 0;
while (i < N) {
if (p.readInt() == 0) {
break;
}
- mList.add(p.readCreator(creator, loader));
+
+ final T parcelable = p.readCreator(creator, loader);
+ if (listElementClass == null) {
+ listElementClass = parcelable.getClass();
+ } else {
+ verifySameType(listElementClass, parcelable.getClass());
+ }
+
+ mList.add(parcelable);
+
if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1));
i++;
}
@@ -82,7 +100,11 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable {
return;
}
while (i < N && reply.readInt() != 0) {
- mList.add(reply.readCreator(creator, loader));
+ final T parcelable = reply.readCreator(creator, loader);
+ verifySameType(listElementClass, parcelable.getClass());
+
+ mList.add(parcelable);
+
if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
i++;
}
@@ -91,6 +113,14 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable {
}
}
+ private static void verifySameType(final Class<?> expected, final Class<?> actual) {
+ if (!actual.equals(expected)) {
+ throw new IllegalArgumentException("Can't unparcel type "
+ + actual.getName() + " in list of type "
+ + expected.getName());
+ }
+ }
+
public List<T> getList() {
return mList;
}
@@ -116,11 +146,16 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable {
dest.writeInt(N);
if (DEBUG) Log.d(TAG, "Writing " + N + " items");
if (N > 0) {
+ final Class<?> listElementClass = mList.get(0).getClass();
dest.writeParcelableCreator(mList.get(0));
int i = 0;
while (i < N && dest.dataSize() < MAX_FIRST_IPC_SIZE) {
dest.writeInt(1);
- mList.get(i).writeToParcel(dest, callFlags);
+
+ final T parcelable = mList.get(i);
+ verifySameType(listElementClass, parcelable.getClass());
+ parcelable.writeToParcel(dest, callFlags);
+
if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
i++;
}
@@ -137,7 +172,11 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable {
if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N);
while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
reply.writeInt(1);
- mList.get(i).writeToParcel(reply, callFlags);
+
+ final T parcelable = mList.get(i);
+ verifySameType(listElementClass, parcelable.getClass());
+ parcelable.writeToParcel(reply, callFlags);
+
if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
i++;
}
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 4a743a5..391ef22 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -35,6 +35,7 @@ import android.util.SparseArray;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
@@ -155,9 +156,19 @@ public abstract class RegisteredServicesCache<V> {
// package is going away, but it's the middle of an upgrade: keep the current
// state and do nothing here. This clause is intentionally empty.
} else {
+ int[] uids = null;
// either we're adding/changing, or it's a removal without replacement, so
- // we need to recalculate the set of available services
- generateServicesMap(userId);
+ // we need to update the set of available services
+ if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)
+ || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+ uids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+ } else {
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ if (uid > 0) {
+ uids = new int[] { uid };
+ }
+ }
+ generateServicesMap(uids, userId);
}
}
@@ -270,7 +281,7 @@ public abstract class RegisteredServicesCache<V> {
// Find user and lazily populate cache
final UserServices<V> user = findOrCreateUserLocked(userId);
if (user.services == null) {
- generateServicesMap(userId);
+ generateServicesMap(null, userId);
}
return user.services.get(type);
}
@@ -285,7 +296,7 @@ public abstract class RegisteredServicesCache<V> {
// Find user and lazily populate cache
final UserServices<V> user = findOrCreateUserLocked(userId);
if (user.services == null) {
- generateServicesMap(userId);
+ generateServicesMap(null, userId);
}
return Collections.unmodifiableCollection(
new ArrayList<ServiceInfo<V>>(user.services.values()));
@@ -311,10 +322,13 @@ public abstract class RegisteredServicesCache<V> {
/**
* Populate {@link UserServices#services} by scanning installed packages for
* given {@link UserHandle}.
+ * @param changedUids the array of uids that have been affected, as mentioned in the broadcast
+ * or null to assume that everything is affected.
+ * @param userId the user for whom to update the services map.
*/
- private void generateServicesMap(int userId) {
+ private void generateServicesMap(int[] changedUids, int userId) {
if (DEBUG) {
- Slog.d(TAG, "generateServicesMap() for " + userId);
+ Slog.d(TAG, "generateServicesMap() for " + userId + ", changed UIDs = " + changedUids);
}
final PackageManager pm = mContext.getPackageManager();
@@ -341,8 +355,6 @@ public abstract class RegisteredServicesCache<V> {
final boolean firstScan = user.services == null;
if (firstScan) {
user.services = Maps.newHashMap();
- } else {
- user.services.clear();
}
StringBuilder changes = new StringBuilder();
@@ -399,7 +411,10 @@ public abstract class RegisteredServicesCache<V> {
ArrayList<V> toBeRemoved = Lists.newArrayList();
for (V v1 : user.persistentServices.keySet()) {
- if (!containsType(serviceInfos, v1)) {
+ // Remove a persisted service that's not in the currently available services list.
+ // And only if it is in the list of changedUids.
+ if (!containsType(serviceInfos, v1)
+ && containsUid(changedUids, user.persistentServices.get(v1))) {
toBeRemoved.add(v1);
}
}
@@ -409,9 +424,20 @@ public abstract class RegisteredServicesCache<V> {
}
changed = true;
user.persistentServices.remove(v1);
+ user.services.remove(v1);
notifyListener(v1, userId, true /* removed */);
}
if (DEBUG) {
+ Log.d(TAG, "user.services=");
+ for (V v : user.services.keySet()) {
+ Log.d(TAG, " " + v + " " + user.services.get(v));
+ }
+ Log.d(TAG, "user.persistentServices=");
+ for (V v : user.persistentServices.keySet()) {
+ Log.d(TAG, " " + v + " " + user.persistentServices.get(v));
+ }
+ }
+ if (DEBUG) {
if (changes.length() > 0) {
Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
serviceInfos.size() + " services:\n" + changes);
@@ -426,6 +452,14 @@ public abstract class RegisteredServicesCache<V> {
}
}
+ /**
+ * Returns true if the list of changed uids is null (wildcard) or the specified uid
+ * is contained in the list of changed uids.
+ */
+ private boolean containsUid(int[] changedUids, int uid) {
+ return changedUids == null || ArrayUtils.contains(changedUids, uid);
+ }
+
private boolean containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type) {
for (int i = 0, N = serviceInfos.size(); i < N; i++) {
if (serviceInfos.get(i).type.equals(type)) {
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index 7edf4b9..fdc54ae 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -22,12 +22,14 @@ import android.os.Parcelable;
import com.android.internal.util.ArrayUtils;
import java.io.ByteArrayInputStream;
+import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
import java.util.Arrays;
/**
@@ -252,4 +254,53 @@ public class Signature implements Parcelable {
return (a.length == b.length) && ArrayUtils.containsAll(a, b)
&& ArrayUtils.containsAll(b, a);
}
+
+ /**
+ * Test if given {@link Signature} sets are effectively equal. In rare
+ * cases, certificates can have slightly malformed encoding which causes
+ * exact-byte checks to fail.
+ * <p>
+ * To identify effective equality, we bounce the certificates through an
+ * decode/encode pass before doing the exact-byte check. To reduce attack
+ * surface area, we only allow a byte size delta of a few bytes.
+ *
+ * @throws CertificateException if the before/after length differs
+ * substantially, usually a signal of something fishy going on.
+ * @hide
+ */
+ public static boolean areEffectiveMatch(Signature[] a, Signature[] b)
+ throws CertificateException {
+ final CertificateFactory cf = CertificateFactory.getInstance("X.509");
+
+ final Signature[] aPrime = new Signature[a.length];
+ for (int i = 0; i < a.length; i++) {
+ aPrime[i] = bounce(cf, a[i]);
+ }
+ final Signature[] bPrime = new Signature[b.length];
+ for (int i = 0; i < b.length; i++) {
+ bPrime[i] = bounce(cf, b[i]);
+ }
+
+ return areExactMatch(aPrime, bPrime);
+ }
+
+ /**
+ * Bounce the given {@link Signature} through a decode/encode cycle.
+ *
+ * @throws CertificateException if the before/after length differs
+ * substantially, usually a signal of something fishy going on.
+ * @hide
+ */
+ public static Signature bounce(CertificateFactory cf, Signature s) throws CertificateException {
+ final InputStream is = new ByteArrayInputStream(s.mSignature);
+ final X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
+ final Signature sPrime = new Signature(cert.getEncoded());
+
+ if (Math.abs(sPrime.mSignature.length - s.mSignature.length) > 2) {
+ throw new CertificateException("Bounced cert length looks fishy; before "
+ + s.mSignature.length + ", after " + sPrime.mSignature.length);
+ }
+
+ return sPrime;
+ }
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 72f24d3..a176593 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -680,6 +680,14 @@ public final class AssetManager implements AutoCloseable {
/**
* Get the locales that this asset manager contains data for.
+ *
+ * <p>On SDK 21 (Android 5.0: Lollipop) and above, Locale strings are valid
+ * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> language tags and can be
+ * parsed using {@link java.util.Locale#forLanguageTag(String)}.
+ *
+ * <p>On SDK 20 (Android 4.4W: Kitkat for watches) and below, locale strings
+ * are of the form {@code ll_CC} where {@code ll} is a two letter language code,
+ * and {@code CC} is a two letter country code.
*/
public native final String[] getLocales();
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 3c290f7..68a39d3 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -353,7 +353,7 @@ public class ColorStateList implements Parcelable {
for (int i = 0; i < inputStates.length; i++) {
final int[] inputState = inputStates[i];
for (int j = 0; j < inputState.length; j++) {
- if (inputState[i] == state) {
+ if (inputState[j] == state) {
return colorStateList;
}
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 27bbb24..14af584 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1365,13 +1365,13 @@ public final class Configuration implements Parcelable, Comparable<Configuration
ArrayList<String> parts = new ArrayList<String>();
if (config.mcc != 0) {
- parts.add(config.mcc + "mcc");
+ parts.add("mcc" + config.mcc);
if (config.mnc != 0) {
- parts.add(config.mnc + "mnc");
+ parts.add("mnc" + config.mnc);
}
}
- if (!config.locale.getLanguage().isEmpty()) {
+ if (config.locale != null && !config.locale.getLanguage().isEmpty()) {
parts.add(localeToResourceQualifier(config.locale));
}
diff --git a/core/java/android/content/res/ConfigurationBoundResourceCache.java b/core/java/android/content/res/ConfigurationBoundResourceCache.java
new file mode 100644
index 0000000..cde7e84
--- /dev/null
+++ b/core/java/android/content/res/ConfigurationBoundResourceCache.java
@@ -0,0 +1,138 @@
+/*
+* Copyright (C) 2014 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR 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.res;
+
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+import java.lang.ref.WeakReference;
+
+/**
+ * A Cache class which can be used to cache resource objects that are easy to clone but more
+ * expensive to inflate.
+ * @hide
+ */
+public class ConfigurationBoundResourceCache<T> {
+
+ private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState<T>>>> mCache =
+ new ArrayMap<String, LongSparseArray<WeakReference<ConstantState<T>>>>();
+
+ final Resources mResources;
+
+ /**
+ * Creates a Resource cache for the given Resources instance.
+ *
+ * @param resources The Resource which can be used when creating new instances.
+ */
+ public ConfigurationBoundResourceCache(Resources resources) {
+ mResources = resources;
+ }
+
+ /**
+ * Adds a new item to the cache.
+ *
+ * @param key A custom key that uniquely identifies the resource.
+ * @param theme The Theme instance where this resource was loaded.
+ * @param constantState The constant state that can create new instances of the resource.
+ *
+ */
+ public void put(long key, Resources.Theme theme, ConstantState<T> constantState) {
+ if (constantState == null) {
+ return;
+ }
+ final String themeKey = theme == null ? "" : theme.getKey();
+ LongSparseArray<WeakReference<ConstantState<T>>> themedCache;
+ synchronized (this) {
+ themedCache = mCache.get(themeKey);
+ if (themedCache == null) {
+ themedCache = new LongSparseArray<WeakReference<ConstantState<T>>>(1);
+ mCache.put(themeKey, themedCache);
+ }
+ themedCache.put(key, new WeakReference<ConstantState<T>>(constantState));
+ }
+ }
+
+ /**
+ * If the resource is cached, creates a new instance of it and returns.
+ *
+ * @param key The long key which can be used to uniquely identify the resource.
+ * @param theme The The Theme instance where we want to load this resource.
+ *
+ * @return If this resources was loaded before, returns a new instance of it. Otherwise, returns
+ * null.
+ */
+ public T get(long key, Resources.Theme theme) {
+ final String themeKey = theme != null ? theme.getKey() : "";
+ final LongSparseArray<WeakReference<ConstantState<T>>> themedCache;
+ final WeakReference<ConstantState<T>> wr;
+ synchronized (this) {
+ themedCache = mCache.get(themeKey);
+ if (themedCache == null) {
+ return null;
+ }
+ wr = themedCache.get(key);
+ }
+ if (wr == null) {
+ return null;
+ }
+ final ConstantState entry = wr.get();
+ if (entry != null) {
+ return (T) entry.newInstance(mResources, theme);
+ } else { // our entry has been purged
+ synchronized (this) {
+ // there is a potential race condition here where this entry may be put in
+ // another thread. But we prefer it to minimize lock duration
+ themedCache.delete(key);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Users of ConfigurationBoundResourceCache must call this method whenever a configuration
+ * change happens. On this callback, the cache invalidates all resources that are not valid
+ * anymore.
+ *
+ * @param configChanges The configuration changes
+ */
+ public void onConfigurationChange(final int configChanges) {
+ synchronized (this) {
+ final int size = mCache.size();
+ for (int i = size - 1; i >= 0; i--) {
+ final LongSparseArray<WeakReference<ConstantState<T>>>
+ themeCache = mCache.valueAt(i);
+ onConfigurationChangeInt(themeCache, configChanges);
+ if (themeCache.size() == 0) {
+ mCache.removeAt(i);
+ }
+ }
+ }
+ }
+
+ private void onConfigurationChangeInt(
+ final LongSparseArray<WeakReference<ConstantState<T>>> themeCache,
+ final int configChanges) {
+ final int size = themeCache.size();
+ for (int i = size - 1; i >= 0; i--) {
+ final WeakReference<ConstantState<T>> wr = themeCache.valueAt(i);
+ final ConstantState<T> constantState = wr.get();
+ if (constantState == null || Configuration.needNewResources(
+ configChanges, constantState.getChangingConfigurations())) {
+ themeCache.removeAt(i);
+ }
+ }
+ }
+
+}
diff --git a/core/java/android/content/res/ConstantState.java b/core/java/android/content/res/ConstantState.java
new file mode 100644
index 0000000..ee609df
--- /dev/null
+++ b/core/java/android/content/res/ConstantState.java
@@ -0,0 +1,61 @@
+/*
+* Copyright (C) 2014 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package android.content.res;
+
+/**
+ * A cache class that can provide new instances of a particular resource which may change
+ * depending on the current {@link Resources.Theme} or {@link Configuration}.
+ * <p>
+ * A constant state should be able to return a bitmask of changing configurations, which
+ * identifies the type of configuration changes that may invalidate this resource. These
+ * configuration changes can be obtained from {@link android.util.TypedValue}. Entities such as
+ * {@link android.animation.Animator} also provide a changing configuration method to include
+ * their dependencies (e.g. An AnimatorSet's changing configuration is the union of the
+ * changing configurations of each Animator in the set)
+ * @hide
+ */
+abstract public class ConstantState<T> {
+
+ /**
+ * Return a bit mask of configuration changes that will impact
+ * this resource (and thus require completely reloading it).
+ */
+ abstract public int getChangingConfigurations();
+
+ /**
+ * Create a new instance without supplying resources the caller
+ * is running in.
+ */
+ public abstract T newInstance();
+
+ /**
+ * Create a new instance from its constant state. This
+ * must be implemented for resources that change based on the target
+ * density of their caller (that is depending on whether it is
+ * in compatibility mode).
+ */
+ public T newInstance(Resources res) {
+ return newInstance();
+ }
+
+ /**
+ * Create a new instance from its constant state. This must be
+ * implemented for resources that can have a theme applied.
+ */
+ public T newInstance(Resources res, Resources.Theme theme) {
+ return newInstance(res);
+ }
+}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 7f276c2..73913b6 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -16,6 +16,9 @@
package android.content.res;
+import android.animation.Animator;
+import android.animation.StateListAnimator;
+import android.annotation.NonNull;
import android.util.Pools.SynchronizedPool;
import android.view.ViewDebug;
import com.android.internal.util.XmlUtils;
@@ -115,6 +118,10 @@ public class Resources {
new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>();
private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache =
new LongSparseArray<WeakReference<ColorStateList>>();
+ private final ConfigurationBoundResourceCache<Animator> mAnimatorCache =
+ new ConfigurationBoundResourceCache<Animator>(this);
+ private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
+ new ConfigurationBoundResourceCache<StateListAnimator>(this);
private TypedValue mTmpValue = new TypedValue();
private boolean mPreloading;
@@ -183,6 +190,24 @@ public class Resources {
}
/**
+ * Used by AnimatorInflater.
+ *
+ * @hide
+ */
+ public ConfigurationBoundResourceCache<Animator> getAnimatorCache() {
+ return mAnimatorCache;
+ }
+
+ /**
+ * Used by AnimatorInflater.
+ *
+ * @hide
+ */
+ public ConfigurationBoundResourceCache<StateListAnimator> getStateListAnimatorCache() {
+ return mStateListAnimatorCache;
+ }
+
+ /**
* This exception is thrown by the resource APIs when a requested resource
* can not be found.
*/
@@ -719,10 +744,13 @@ public class Resources {
* @throws NotFoundException Throws NotFoundException if the given ID does
* not exist.
* @see #getDrawable(int, Theme)
+ * @deprecated Use {@link #getDrawable(int, Theme)} instead.
*/
+ @Deprecated
+ @Nullable
public Drawable getDrawable(int id) throws NotFoundException {
final Drawable d = getDrawable(id, null);
- if (d.canApplyTheme()) {
+ if (d != null && d.canApplyTheme()) {
Log.w(TAG, "Drawable " + getResourceName(id) + " has unresolved theme "
+ "attributes! Consider using Resources.getDrawable(int, Theme) or "
+ "Context.getDrawable(int).", new RuntimeException());
@@ -744,6 +772,7 @@ public class Resources {
* @throws NotFoundException Throws NotFoundException if the given ID does
* not exist.
*/
+ @Nullable
public Drawable getDrawable(int id, @Nullable Theme theme) throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
@@ -788,7 +817,10 @@ public class Resources {
* @throws NotFoundException Throws NotFoundException if the given ID does
* not exist.
* @see #getDrawableForDensity(int, int, Theme)
+ * @deprecated Use {@link #getDrawableForDensity(int, int, Theme)} instead.
*/
+ @Deprecated
+ @Nullable
public Drawable getDrawableForDensity(int id, int density) throws NotFoundException {
return getDrawableForDensity(id, density, null);
}
@@ -807,6 +839,7 @@ public class Resources {
* @throws NotFoundException Throws NotFoundException if the given ID does
* not exist.
*/
+ @Nullable
public Drawable getDrawableForDensity(int id, int density, @Nullable Theme theme) {
TypedValue value;
synchronized (mAccessLock) {
@@ -1524,20 +1557,21 @@ public class Resources {
* contents of the typed array are ultimately filled in by
* {@link Resources#getValue}.
*
- * @param values The base set of attribute values, must be equal
- * in length to {@code attrs} or {@code null}. All values
- * must be of type {@link TypedValue#TYPE_ATTRIBUTE}.
+ * @param values The base set of attribute values, must be equal in
+ * length to {@code attrs}. All values must be of type
+ * {@link TypedValue#TYPE_ATTRIBUTE}.
* @param attrs The desired attributes to be retrieved.
* @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) {
+ @NonNull
+ public TypedArray resolveAttributes(@NonNull int[] values, @NonNull int[] attrs) {
final int len = attrs.length;
- if (values != null && len != values.length) {
+ if (values == null || len != values.length) {
throw new IllegalArgumentException(
- "Base attribute values must be null or the same length as attrs");
+ "Base attribute values must the same length as attrs");
}
final TypedArray array = TypedArray.obtain(Resources.this, len);
@@ -1761,23 +1795,7 @@ public class Resources {
// the framework.
mCompatibilityInfo.applyToDisplayMetrics(mMetrics);
- int configChanges = 0xfffffff;
- if (config != null) {
- mTmpConfig.setTo(config);
- int density = config.densityDpi;
- if (density == Configuration.DENSITY_DPI_UNDEFINED) {
- density = mMetrics.noncompatDensityDpi;
- }
-
- mCompatibilityInfo.applyToConfiguration(density, mTmpConfig);
-
- if (mTmpConfig.locale == null) {
- mTmpConfig.locale = Locale.getDefault();
- mTmpConfig.setLayoutDirection(mTmpConfig.locale);
- }
- configChanges = mConfiguration.updateFrom(mTmpConfig);
- configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
- }
+ int configChanges = calcConfigChanges(config);
if (mConfiguration.locale == null) {
mConfiguration.locale = Locale.getDefault();
mConfiguration.setLayoutDirection(mConfiguration.locale);
@@ -1825,6 +1843,8 @@ public class Resources {
clearDrawableCachesLocked(mDrawableCache, configChanges);
clearDrawableCachesLocked(mColorDrawableCache, configChanges);
+ mAnimatorCache.onConfigurationChange(configChanges);
+ mStateListAnimatorCache.onConfigurationChange(configChanges);
mColorStateListCache.clear();
@@ -1837,6 +1857,30 @@ public class Resources {
}
}
+ /**
+ * Called by ConfigurationBoundResourceCacheTest via reflection.
+ */
+ private int calcConfigChanges(Configuration config) {
+ int configChanges = 0xfffffff;
+ if (config != null) {
+ mTmpConfig.setTo(config);
+ int density = config.densityDpi;
+ if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+ density = mMetrics.noncompatDensityDpi;
+ }
+
+ mCompatibilityInfo.applyToConfiguration(density, mTmpConfig);
+
+ if (mTmpConfig.locale == null) {
+ mTmpConfig.locale = Locale.getDefault();
+ mTmpConfig.setLayoutDirection(mTmpConfig.locale);
+ }
+ configChanges = mConfiguration.updateFrom(mTmpConfig);
+ configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
+ }
+ return configChanges;
+ }
+
private void clearDrawableCachesLocked(
ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches,
int configChanges) {
@@ -2323,7 +2367,14 @@ public class Resources {
final Drawable dr;
if (cs != null) {
- dr = cs.newDrawable(this, theme);
+ final Drawable clonedDr = cs.newDrawable(this);
+ if (theme != null) {
+ dr = clonedDr.mutate();
+ dr.applyTheme(theme);
+ dr.clearMutated();
+ } else {
+ dr = clonedDr;
+ }
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java
index e0f1b3a..4ae3825 100644
--- a/core/java/android/content/res/ResourcesKey.java
+++ b/core/java/android/content/res/ResourcesKey.java
@@ -62,10 +62,15 @@ public final class ResourcesKey {
return false;
}
ResourcesKey peer = (ResourcesKey) obj;
- if (mResDir != peer.mResDir) {
- if (mResDir == null || peer.mResDir == null) {
- return false;
- } else if (!mResDir.equals(peer.mResDir)) {
+
+ if ((mResDir == null) && (peer.mResDir != null)) {
+ return false;
+ }
+ if ((mResDir != null) && (peer.mResDir == null)) {
+ return false;
+ }
+ if ((mResDir != null) && (peer.mResDir != null)) {
+ if (!mResDir.equals(peer.mResDir)) {
return false;
}
}
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 73b93c6..02602fb 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -807,6 +807,9 @@ public class TypedArray {
/**
* Determines whether there is an attribute at <var>index</var>.
+ * <p>
+ * <strong>Note:</strong> If the attribute was set to {@code @empty} or
+ * {@code @undefined}, this method returns {@code false}.
*
* @param index Index of attribute to retrieve.
*
@@ -824,6 +827,27 @@ public class TypedArray {
}
/**
+ * Determines whether there is an attribute at <var>index</var>, returning
+ * {@code true} if the attribute was explicitly set to {@code @empty} and
+ * {@code false} only if the attribute was undefined.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return True if the attribute has a value or is empty, false otherwise.
+ */
+ public boolean hasValueOrEmpty(int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ return type != TypedValue.TYPE_NULL
+ || data[index+AssetManager.STYLE_DATA] == TypedValue.DATA_NULL_EMPTY;
+ }
+
+ /**
* Retrieve the raw TypedValue for the attribute at <var>index</var>
* and return a temporary object holding its data. This object is only
* valid until the next call on to {@link TypedArray}.
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index f514e42..fa5e9d2 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -18,6 +18,7 @@
package android.hardware;
import android.os.Build;
+import android.annotation.SystemApi;
/**
* Class representing a sensor. Use {@link SensorManager#getSensorList} to get
@@ -329,7 +330,11 @@ public final class Sensor {
* A sensor of this type triggers an event each time a step is taken by the user. The only
* allowed value to return is 1.0 and an event is generated for each step. Like with any other
* event, the timestamp indicates when the event (here the step) occurred, this corresponds to
- * when the foot hit the ground, generating a high variation in acceleration.
+ * when the foot hit the ground, generating a high variation in acceleration. This sensor is
+ * only for detecting every individual step as soon as it is taken, for example to perform dead
+ * reckoning. If you only need aggregate number of steps taken over a period of time, register
+ * for {@link #TYPE_STEP_COUNTER} instead. It is defined as a
+ * {@link Sensor#REPORTING_MODE_SPECIAL_TRIGGER} sensor.
* <p>
* See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details.
*/
@@ -349,7 +354,12 @@ public final class Sensor {
* while activated. The value is returned as a float (with the fractional part set to zero) and
* is reset to zero only on a system reboot. The timestamp of the event is set to the time when
* the last step for that event was taken. This sensor is implemented in hardware and is
- * expected to be low power.
+ * expected to be low power. If you want to continuously track the number of steps over a long
+ * period of time, do NOT unregister for this sensor, so that it keeps counting steps in the
+ * background even when the AP is in suspend mode and report the aggregate count when the AP
+ * is awake. Application needs to stay registered for this sensor because step counter does not
+ * count steps if it is not activated. This sensor is ideal for fitness tracking applications.
+ * It is defined as an {@link Sensor#REPORTING_MODE_ON_CHANGE} sensor.
* <p>
* See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details.
*/
@@ -502,6 +512,27 @@ public final class Sensor {
*/
public static final String STRING_TYPE_PICK_UP_GESTURE = "android.sensor.pick_up_gesture";
+ /**
+ * A constant describing a wrist tilt gesture sensor.
+ *
+ * A sensor of this type triggers when the device face is tilted towards the user.
+ * The only allowed return value is 1.0.
+ * This sensor remains active until disabled.
+ *
+ * @hide This sensor is expected to only be used by the system ui
+ */
+ @SystemApi
+ public static final int TYPE_WRIST_TILT_GESTURE = 26;
+
+ /**
+ * A constant string describing a wrist tilt gesture sensor.
+ *
+ * @hide This sensor is expected to only be used by the system ui
+ * @see #TYPE_WRIST_TILT_GESTURE
+ */
+ @SystemApi
+ public static final String STRING_TYPE_WRIST_TILT_GESTURE = "android.sensor.wrist_tilt_gesture";
+
/**
* A constant describing all sensor types.
*/
@@ -582,6 +613,7 @@ public final class Sensor {
1, // SENSOR_TYPE_WAKE_GESTURE
1, // SENSOR_TYPE_GLANCE_GESTURE
1, // SENSOR_TYPE_PICK_UP_GESTURE
+ 1, // SENSOR_TYPE_WRIST_TILT_GESTURE
};
/**
@@ -750,31 +782,41 @@ public final class Sensor {
}
/**
- * 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.
+ * Returns true if the sensor is a wake-up sensor.
* <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.
+ * <b>Application Processor Power modes</b> <p>
+ * Application Processor(AP), is the processor on which applications run. When no wake lock is held
+ * and the user is not interacting with the device, this processor can enter a “Suspend” mode,
+ * reducing the power consumption by 10 times or more.
+ * </p>
* <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.
+ * <b>Non-wake-up sensors</b> <p>
+ * Non-wake-up sensors are sensors that do not wake the AP out of suspend to report data. While
+ * the AP is in suspend mode, the sensors continue to function and generate events, which are
+ * put in a hardware FIFO. The events in the FIFO are delivered to the application when the AP
+ * wakes up. If the FIFO was too small to store all events generated while the AP was in
+ * suspend mode, the older events are lost: the oldest data is dropped to accommodate the newer
+ * data. In the extreme case where the FIFO is non-existent {@code maxFifoEventCount() == 0},
+ * all events generated while the AP was in suspend mode are lost. Applications using
+ * non-wake-up sensors should usually:
+ * <ul>
+ * <li>Either unregister from the sensors when they do not need them, usually in the activity’s
+ * {@code onPause} method. This is the most common case.
+ * <li>Or realize that the sensors are consuming some power while the AP is in suspend mode and
+ * that even then, some events might be lost.
+ * </ul>
+ * </p>
* <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.
+ * <b>Wake-up sensors</b> <p>
+ * In opposition to non-wake-up sensors, wake-up sensors ensure that their data is delivered
+ * independently of the state of the AP. While the AP is awake, the wake-up sensors behave
+ * like non-wake-up-sensors. When the AP is asleep, wake-up sensors wake up the AP to deliver
+ * events. That is, the AP will wake up and the sensor will deliver the events before the
+ * maximum reporting latency is elapsed or the hardware FIFO gets full. See {@link
+ * SensorManager#registerListener(SensorEventListener, Sensor, int, int)} for more details.
+ * </p>
+ *
+ * @return <code>true</code> if this is a wake-up sensor, <code>false</code> otherwise.
*/
public boolean isWakeUpSensor() {
return (mFlags & SENSOR_FLAG_WAKE_UP_SENSOR) != 0;
diff --git a/core/java/android/hardware/SensorEventListener2.java b/core/java/android/hardware/SensorEventListener2.java
index 70eff08..fd3e62b 100644
--- a/core/java/android/hardware/SensorEventListener2.java
+++ b/core/java/android/hardware/SensorEventListener2.java
@@ -21,15 +21,16 @@ package android.hardware;
*/
public interface SensorEventListener2 extends SensorEventListener {
/**
- * Called after flush() is completed. All the events in the batch at the point when
- * the flush was called have been delivered to the applications registered for those
- * sensor events. Flush Complete Events are sent ONLY to the application that has
- * explicitly called flush(). If the hardware FIFO is flushed due to some other
- * application calling flush(), flush complete event is not delivered to this application.
+ * Called after flush() is completed. All the events in the batch at the point when the flush
+ * was called have been delivered to the applications registered for those sensor events. In
+ * {@link android.os.Build.VERSION_CODES#KITKAT}, applications may receive flush complete events
+ * even if some other application has called flush() on the same sensor. Starting with
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP}, flush Complete events are sent ONLY to the
+ * application that has explicitly called flush(). If the hardware FIFO is flushed due to some
+ * other application calling flush(), flush complete event is not delivered to this application.
* <p>
*
* @param sensor The {@link android.hardware.Sensor Sensor} on which flush was called.
- *
* @see android.hardware.SensorManager#flush(SensorEventListener)
*/
public void onFlushCompleted(Sensor sensor);
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index cccd624..34b895b 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -451,7 +451,8 @@ public abstract class SensorManager {
// non_wake-up version.
if (type == Sensor.TYPE_PROXIMITY || type == Sensor.TYPE_SIGNIFICANT_MOTION ||
type == Sensor.TYPE_TILT_DETECTOR || type == Sensor.TYPE_WAKE_GESTURE ||
- type == Sensor.TYPE_GLANCE_GESTURE || type == Sensor.TYPE_PICK_UP_GESTURE) {
+ type == Sensor.TYPE_GLANCE_GESTURE || type == Sensor.TYPE_PICK_UP_GESTURE ||
+ type == Sensor.TYPE_WRIST_TILT_GESTURE) {
wakeUpSensor = true;
}
@@ -626,73 +627,90 @@ public abstract class SensorManager {
protected abstract void unregisterListenerImpl(SensorEventListener listener, Sensor sensor);
/**
- * Registers a {@link android.hardware.SensorEventListener
- * SensorEventListener} for the given sensor.
- *
- * <p class="note"></p>
- * Note: Don't use this method with a one shot trigger sensor such as
- * {@link Sensor#TYPE_SIGNIFICANT_MOTION}.
- * Use {@link #requestTriggerSensor(TriggerEventListener, Sensor)} instead.
+ * Registers a {@link android.hardware.SensorEventListener SensorEventListener} for the given
+ * sensor at the given sampling frequency.
+ * <p>
+ * The events will be delivered to the provided {@code SensorEventListener} as soon as they are
+ * available. To reduce the power consumption, applications can use
+ * {@link #registerListener(SensorEventListener, Sensor, int, int)} instead and specify a
+ * positive non-zero maximum reporting latency.
+ * </p>
+ * <p>
+ * In the case of non-wake-up sensors, the events are only delivered while the Application
+ * Processor (AP) is not in suspend mode. See {@link Sensor#isWakeUpSensor()} for more details.
+ * To ensure delivery of events from non-wake-up sensors even when the screen is OFF, the
+ * application registering to the sensor must hold a partial wake-lock to keep the AP awake,
+ * otherwise some events might be lost while the AP is asleep. Note that although events might
+ * be lost while the AP is asleep, the sensor will still consume power if it is not explicitly
+ * deactivated by the application. Applications must unregister their {@code
+ * SensorEventListener}s in their activity's {@code onPause()} method to avoid consuming power
+ * while the device is inactive. See {@link #registerListener(SensorEventListener, Sensor, int,
+ * int)} for more details on hardware FIFO (queueing) capabilities and when some sensor events
+ * might be lost.
+ * </p>
+ * <p>
+ * In the case of wake-up sensors, each event generated by the sensor will cause the AP to
+ * wake-up, ensuring that each event can be delivered. Because of this, registering to a wake-up
+ * sensor has very significant power implications. Call {@link Sensor#isWakeUpSensor()} to check
+ * whether a sensor is a wake-up sensor. See
+ * {@link #registerListener(SensorEventListener, Sensor, int, int)} for information on how to
+ * reduce the power impact of registering to wake-up sensors.
+ * </p>
+ * <p class="note">
+ * Note: Don't use this method with one-shot trigger sensors such as
+ * {@link Sensor#TYPE_SIGNIFICANT_MOTION}. Use
+ * {@link #requestTriggerSensor(TriggerEventListener, Sensor)} instead. Use
+ * {@link Sensor#getReportingMode()} to obtain the reporting mode of a given sensor.
* </p>
*
- * @param listener
- * A {@link android.hardware.SensorEventListener SensorEventListener}
- * object.
- *
- * @param sensor
- * The {@link android.hardware.Sensor Sensor} to register to.
- *
- * @param rateUs
- * The rate {@link android.hardware.SensorEvent sensor events} are
- * delivered at. This is only a hint to the system. Events may be
- * received faster or slower than the specified rate. Usually events
- * are received faster. The value must be one of
- * {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI},
- * {@link #SENSOR_DELAY_GAME}, or {@link #SENSOR_DELAY_FASTEST}
- * or, the desired delay between events in microseconds.
- * Specifying the delay in microseconds only works from Android
- * 2.3 (API level 9) onwards. For earlier releases, you must use
- * one of the {@code SENSOR_DELAY_*} constants.
- *
- * @return <code>true</code> if the sensor is supported and successfully
- * enabled.
- *
+ * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object.
+ * @param sensor The {@link android.hardware.Sensor Sensor} to register to.
+ * @param samplingPeriodUs The rate {@link android.hardware.SensorEvent sensor events} are
+ * delivered at. This is only a hint to the system. Events may be received faster or
+ * slower than the specified rate. Usually events are received faster. The value must
+ * be one of {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI},
+ * {@link #SENSOR_DELAY_GAME}, or {@link #SENSOR_DELAY_FASTEST} or, the desired delay
+ * between events in microseconds. Specifying the delay in microseconds only works
+ * from Android 2.3 (API level 9) onwards. For earlier releases, you must use one of
+ * the {@code SENSOR_DELAY_*} constants.
+ * @return <code>true</code> if the sensor is supported and successfully enabled.
* @see #registerListener(SensorEventListener, Sensor, int, Handler)
* @see #unregisterListener(SensorEventListener)
* @see #unregisterListener(SensorEventListener, Sensor)
- *
*/
- public boolean registerListener(SensorEventListener listener, Sensor sensor, int rateUs) {
- return registerListener(listener, sensor, rateUs, null);
+ public boolean registerListener(SensorEventListener listener, Sensor sensor,
+ int samplingPeriodUs) {
+ return registerListener(listener, sensor, samplingPeriodUs, null);
}
/**
- * Enables batch mode for a sensor with the given rate and maxBatchReportLatency. If the
- * underlying hardware does not support batch mode, this defaults to
- * {@link #registerListener(SensorEventListener, Sensor, int)} and other parameters are
- * ignored. In non-batch mode, all sensor events must be reported as soon as they are detected.
- * While in batch mode, sensor events do not need to be reported as soon as they are detected.
- * They can be temporarily stored in batches and reported in batches, as long as no event is
- * delayed by more than "maxBatchReportLatency" microseconds. That is, all events since the
- * previous batch are recorded and returned all at once. This allows to reduce the amount of
- * interrupts sent to the SoC, and allows the SoC to switch to a lower power state (Idle) while
- * the sensor is capturing and batching data.
- * <p>
- * Registering to a sensor in batch mode will not prevent the SoC from going to suspend mode. In
- * this case, the sensor will continue to gather events and store it in a hardware FIFO. If the
- * FIFO gets full before the AP wakes up again, some events will be lost, as the older events
- * get overwritten by new events in the hardware FIFO. This can be avoided by holding a wake
- * lock. If the application holds a wake lock, the SoC will not go to suspend mode, so no events
- * will be lost, as the events will be reported before the FIFO gets full.
- * </p>
+ * Registers a {@link android.hardware.SensorEventListener SensorEventListener} for the given
+ * sensor at the given sampling frequency and the given maximum reporting latency.
* <p>
- * Batching is always best effort. If a different application requests updates in continuous
- * mode, this application will also get events in continuous mode. Batch mode updates can be
- * unregistered by calling {@link #unregisterListener(SensorEventListener)}.
+ * This function is similar to {@link #registerListener(SensorEventListener, Sensor, int)} but
+ * it allows events to stay temporarily in the hardware FIFO (queue) before being delivered. The
+ * events can be stored in the hardware FIFO up to {@code maxReportLatencyUs} microseconds. Once
+ * one of the events in the FIFO needs to be reported, all of the events in the FIFO are
+ * reported sequentially. This means that some events will be reported before the maximum
+ * reporting latency has elapsed.
+ * </p><p>
+ * When {@code maxReportLatencyUs} is 0, the call is equivalent to a call to
+ * {@link #registerListener(SensorEventListener, Sensor, int)}, as it requires the events to be
+ * delivered as soon as possible.
+ * </p><p>
+ * When {@code sensor.maxFifoEventCount()} is 0, the sensor does not use a FIFO, so the call
+ * will also be equivalent to {@link #registerListener(SensorEventListener, Sensor, int)}.
+ * </p><p>
+ * Setting {@code maxReportLatencyUs} to a positive value allows to reduce the number of
+ * interrupts the AP (Application Processor) receives, hence reducing power consumption, as the
+ * AP can switch to a lower power state while the sensor is capturing the data. This is
+ * especially important when registering to wake-up sensors, for which each interrupt causes the
+ * AP to wake up if it was in suspend mode. See {@link Sensor#isWakeUpSensor()} for more
+ * information on wake-up sensors.
* </p>
* <p class="note">
* </p>
- * Note: Don't use this method with a one shot trigger sensor such as
+ * Note: Don't use this method with one-shot trigger sensors such as
* {@link Sensor#TYPE_SIGNIFICANT_MOTION}. Use
* {@link #requestTriggerSensor(TriggerEventListener, Sensor)} instead. </p>
*
@@ -701,118 +719,104 @@ public abstract class SensorManager {
* flush complete notifications, it should register with
* {@link android.hardware.SensorEventListener SensorEventListener2} instead.
* @param sensor The {@link android.hardware.Sensor Sensor} to register to.
- * @param rateUs The desired delay between two consecutive events in microseconds. This is only
- * a hint to the system. Events may be received faster or slower than the specified
- * rate. Usually events are received faster. Can be one of
+ * @param samplingPeriodUs The desired delay between two consecutive events in microseconds.
+ * This is only a hint to the system. Events may be received faster or slower than
+ * the specified rate. Usually events are received faster. Can be one of
* {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI},
* {@link #SENSOR_DELAY_GAME}, {@link #SENSOR_DELAY_FASTEST} or the delay in
* microseconds.
- * @param maxBatchReportLatencyUs An event in the batch can be delayed by at most
- * maxBatchReportLatency microseconds. More events can be batched if this value is
- * large. If this is set to zero, batch mode is disabled and events are delivered in
- * continuous mode as soon as they are available which is equivalent to calling
+ * @param maxReportLatencyUs Maximum time in microseconds that events can be delayed before
+ * being reported to the application. A large value allows reducing the power
+ * consumption associated with the sensor. If maxReportLatencyUs is set to zero,
+ * events are delivered as soon as they are available, which is equivalent to calling
* {@link #registerListener(SensorEventListener, Sensor, int)}.
- * @return <code>true</code> if batch mode is successfully enabled for this sensor,
- * <code>false</code> otherwise.
+ * @return <code>true</code> if the sensor is supported and successfully enabled.
* @see #registerListener(SensorEventListener, Sensor, int)
* @see #unregisterListener(SensorEventListener)
* @see #flush(SensorEventListener)
*/
- public boolean registerListener(SensorEventListener listener, Sensor sensor, int rateUs,
- int maxBatchReportLatencyUs) {
- int delay = getDelay(rateUs);
- return registerListenerImpl(listener, sensor, delay, null, maxBatchReportLatencyUs, 0);
+ public boolean registerListener(SensorEventListener listener, Sensor sensor,
+ int samplingPeriodUs, int maxReportLatencyUs) {
+ int delay = getDelay(samplingPeriodUs);
+ return registerListenerImpl(listener, sensor, delay, null, maxReportLatencyUs, 0);
}
/**
* Registers a {@link android.hardware.SensorEventListener SensorEventListener} for the given
* sensor. Events are delivered in continuous mode as soon as they are available. To reduce the
- * battery usage, use {@link #registerListener(SensorEventListener, Sensor, int, int)} which
- * enables batch mode for the sensor.
- *
- * <p class="note"></p>
- * Note: Don't use this method with a one shot trigger sensor such as
- * {@link Sensor#TYPE_SIGNIFICANT_MOTION}.
- * Use {@link #requestTriggerSensor(TriggerEventListener, Sensor)} instead.
+ * power consumption, applications can use
+ * {@link #registerListener(SensorEventListener, Sensor, int, int)} instead and specify a
+ * positive non-zero maximum reporting latency.
+ * <p class="note">
* </p>
+ * Note: Don't use this method with a one shot trigger sensor such as
+ * {@link Sensor#TYPE_SIGNIFICANT_MOTION}. Use
+ * {@link #requestTriggerSensor(TriggerEventListener, Sensor)} instead. </p>
*
- * @param listener
- * A {@link android.hardware.SensorEventListener SensorEventListener}
- * object.
- *
- * @param sensor
- * The {@link android.hardware.Sensor Sensor} to register to.
- *
- * @param rateUs
- * The rate {@link android.hardware.SensorEvent sensor events} are
- * delivered at. This is only a hint to the system. Events may be
- * received faster or slower than the specified rate. Usually events
- * are received faster. The value must be one of
- * {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI},
- * {@link #SENSOR_DELAY_GAME}, or {@link #SENSOR_DELAY_FASTEST}.
- * or, the desired delay between events in microseconds.
- * Specifying the delay in microseconds only works from Android
- * 2.3 (API level 9) onwards. For earlier releases, you must use
- * one of the {@code SENSOR_DELAY_*} constants.
- *
- * @param handler
- * The {@link android.os.Handler Handler} the
- * {@link android.hardware.SensorEvent sensor events} will be
- * delivered to.
- *
+ * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object.
+ * @param sensor The {@link android.hardware.Sensor Sensor} to register to.
+ * @param samplingPeriodUs The rate {@link android.hardware.SensorEvent sensor events} are
+ * delivered at. This is only a hint to the system. Events may be received faster or
+ * slower than the specified rate. Usually events are received faster. The value must
+ * be one of {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI},
+ * {@link #SENSOR_DELAY_GAME}, or {@link #SENSOR_DELAY_FASTEST} or, the desired
+ * delay between events in microseconds. Specifying the delay in microseconds only
+ * works from Android 2.3 (API level 9) onwards. For earlier releases, you must use
+ * one of the {@code SENSOR_DELAY_*} constants.
+ * @param handler The {@link android.os.Handler Handler} the {@link android.hardware.SensorEvent
+ * sensor events} will be delivered to.
* @return <code>true</code> if the sensor is supported and successfully enabled.
- *
* @see #registerListener(SensorEventListener, Sensor, int)
* @see #unregisterListener(SensorEventListener)
* @see #unregisterListener(SensorEventListener, Sensor)
*/
- public boolean registerListener(SensorEventListener listener, Sensor sensor, int rateUs,
- Handler handler) {
- int delay = getDelay(rateUs);
+ public boolean registerListener(SensorEventListener listener, Sensor sensor,
+ int samplingPeriodUs, Handler handler) {
+ int delay = getDelay(samplingPeriodUs);
return registerListenerImpl(listener, sensor, delay, handler, 0, 0);
}
/**
- * Enables batch mode for a sensor with the given rate and maxBatchReportLatency.
+ * Registers a {@link android.hardware.SensorEventListener SensorEventListener} for the given
+ * sensor at the given sampling frequency and the given maximum reporting latency.
+ *
* @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object
* that will receive the sensor events. If the application is interested in receiving
* flush complete notifications, it should register with
* {@link android.hardware.SensorEventListener SensorEventListener2} instead.
* @param sensor The {@link android.hardware.Sensor Sensor} to register to.
- * @param rateUs The desired delay between two consecutive events in microseconds. This is only
- * a hint to the system. Events may be received faster or slower than the specified
- * rate. Usually events are received faster. Can be one of
+ * @param samplingPeriodUs The desired delay between two consecutive events in microseconds.
+ * This is only a hint to the system. Events may be received faster or slower than
+ * the specified rate. Usually events are received faster. Can be one of
* {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI},
* {@link #SENSOR_DELAY_GAME}, {@link #SENSOR_DELAY_FASTEST} or the delay in
* microseconds.
- * @param maxBatchReportLatencyUs An event in the batch can be delayed by at most
- * maxBatchReportLatency microseconds. More events can be batched if this value is
- * large. If this is set to zero, batch mode is disabled and events are delivered in
- * continuous mode as soon as they are available which is equivalent to calling
+ * @param maxReportLatencyUs Maximum time in microseconds that events can be delayed before
+ * being reported to the application. A large value allows reducing the power
+ * consumption associated with the sensor. If maxReportLatencyUs is set to zero,
+ * events are delivered as soon as they are available, which is equivalent to calling
* {@link #registerListener(SensorEventListener, Sensor, int)}.
- * @param handler The {@link android.os.Handler Handler} the
- * {@link android.hardware.SensorEvent sensor events} will be delivered to.
- *
- * @return <code>true</code> if batch mode is successfully enabled for this sensor,
- * <code>false</code> otherwise.
+ * @param handler The {@link android.os.Handler Handler} the {@link android.hardware.SensorEvent
+ * sensor events} will be delivered to.
+ * @return <code>true</code> if the sensor is supported and successfully enabled.
* @see #registerListener(SensorEventListener, Sensor, int, int)
*/
- public boolean registerListener(SensorEventListener listener, Sensor sensor, int rateUs,
- int maxBatchReportLatencyUs, Handler handler) {
- int delayUs = getDelay(rateUs);
- return registerListenerImpl(listener, sensor, delayUs, handler, maxBatchReportLatencyUs, 0);
+ public boolean registerListener(SensorEventListener listener, Sensor sensor, int samplingPeriodUs,
+ int maxReportLatencyUs, Handler handler) {
+ int delayUs = getDelay(samplingPeriodUs);
+ return registerListenerImpl(listener, sensor, delayUs, handler, maxReportLatencyUs, 0);
}
/** @hide */
protected abstract boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,
- int delayUs, Handler handler, int maxBatchReportLatencyUs, int reservedFlags);
+ int delayUs, Handler handler, int maxReportLatencyUs, int reservedFlags);
/**
- * Flushes the batch FIFO of all the sensors registered for this listener. If there are events
- * in the FIFO of the sensor, they are returned as if the batch timeout in the FIFO of the
- * sensors had expired. Events are returned in the usual way through the SensorEventListener.
- * This call doesn't affect the batch timeout for this sensor. This call is asynchronous and
+ * Flushes the FIFO of all the sensors registered for this listener. If there are events
+ * in the FIFO of the sensor, they are returned as if the maxReportLantecy of the FIFO has
+ * expired. Events are returned in the usual way through the SensorEventListener.
+ * This call doesn't affect the maxReportLantecy for this sensor. This call is asynchronous and
* returns immediately.
* {@link android.hardware.SensorEventListener2#onFlushCompleted onFlushCompleted} is called
* after all the events in the batch at the time of calling this method have been delivered
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index fb3eaff..7cf8fb0 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -315,16 +315,15 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* supported by this camera device.</p>
* <p>This key lists the valid modes for {@link CaptureRequest#COLOR_CORRECTION_ABERRATION_MODE android.colorCorrection.aberrationMode}. If no
* aberration correction modes are available for a device, this list will solely include
- * OFF mode.</p>
- * <p>For FULL capability device ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} <code>==</code> FULL), OFF is
- * always included.</p>
+ * OFF mode. All camera devices will support either OFF or FAST mode.</p>
+ * <p>Camera devices that support the MANUAL_POST_PROCESSING capability will always list
+ * OFF mode. This includes all FULL level devices.</p>
* <p>LEGACY devices will always only support FAST mode.</p>
* <p><b>Range of valid values:</b><br>
* Any value listed in {@link CaptureRequest#COLOR_CORRECTION_ABERRATION_MODE android.colorCorrection.aberrationMode}</p>
* <p>This key is available on all devices.</p>
*
* @see CaptureRequest#COLOR_CORRECTION_ABERRATION_MODE
- * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
*/
@PublicKey
public static final Key<int[]> COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES =
@@ -337,8 +336,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* supported by a given camera device. This field lists the
* valid anti-banding modes that the application may request
* for this camera device with the
- * {@link CaptureRequest#CONTROL_AE_ANTIBANDING_MODE android.control.aeAntibandingMode} control. This list
- * always includes AUTO.</p>
+ * {@link CaptureRequest#CONTROL_AE_ANTIBANDING_MODE android.control.aeAntibandingMode} control.</p>
* <p><b>Range of valid values:</b><br>
* Any value listed in {@link CaptureRequest#CONTROL_AE_ANTIBANDING_MODE android.control.aeAntibandingMode}</p>
* <p>This key is available on all devices.</p>
@@ -393,8 +391,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* {@link CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION android.control.aeExposureCompensation}, in counts of {@link CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP android.control.aeCompensationStep},
* that are supported by this camera device.</p>
* <p><b>Range of valid values:</b><br></p>
+ * <p>Range [0,0] indicates that exposure compensation is not supported.</p>
+ * <p>For LIMITED and FULL devices, range must follow below requirements if exposure
+ * compensation is supported (<code>range != [0, 0]</code>):</p>
* <p><code>Min.exposure compensation * {@link CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP android.control.aeCompensationStep} &lt;= -2 EV</code></p>
* <p><code>Max.exposure compensation * {@link CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP android.control.aeCompensationStep} &gt;= 2 EV</code></p>
+ * <p>LEGACY devices may support a smaller range than this.</p>
* <p>This key is available on all devices.</p>
*
* @see CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP
@@ -1155,6 +1157,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR MANUAL_SENSOR}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING MANUAL_POST_PROCESSING}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_RAW RAW}</li>
+ * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS READ_SENSOR_SETTINGS}</li>
+ * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE BURST_CAPTURE}</li>
* </ul></p>
* <p>This key is available on all devices.</p>
*
@@ -1163,6 +1167,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR
* @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING
* @see #REQUEST_AVAILABLE_CAPABILITIES_RAW
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE
*/
@PublicKey
public static final Key<int[]> REQUEST_AVAILABLE_CAPABILITIES =
@@ -2283,12 +2289,16 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>Camera devices will come in three flavors: LEGACY, LIMITED and FULL.</p>
* <p>A FULL device will support below capabilities:</p>
* <ul>
- * <li>30fps at maximum resolution (== sensor resolution) is preferred, more than 20fps is required.</li>
+ * <li>30fps operation at maximum resolution (== sensor resolution) is preferred, more than
+ * 20fps is required, for at least uncompressed YUV
+ * output. ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains BURST_CAPTURE)</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>
+ * <li>Manual post-processing control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+ * MANUAL_POST_PROCESSING)</li>
* <li>Arbitrary cropping region ({@link CameraCharacteristics#SCALER_CROPPING_TYPE android.scaler.croppingType} <code>==</code> FREEFORM)</li>
- * <li>At least 3 processed (but not stalling) format output streams ({@link CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_PROC android.request.maxNumOutputProc} <code>&gt;=</code> 3)</li>
+ * <li>At least 3 processed (but not stalling) format output streams
+ * ({@link CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_PROC android.request.maxNumOutputProc} <code>&gt;=</code> 3)</li>
* <li>The required stream configuration defined in android.scaler.availableStreamConfigurations</li>
* <li>The required exposure time range defined in {@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}</li>
* <li>The required maxFrameDuration defined in {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li>
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 5cfa821..bec9489 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -147,20 +147,20 @@ public abstract class CameraDevice implements AutoCloseable {
* <ul>
*
* <li>For drawing to a {@link android.view.SurfaceView SurfaceView}: Once the SurfaceView's
- * Surface is {@link android.view.SurfaceHolder.Callback#surfaceCreated created}, 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(SurfaceHolder.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
+ * Surface is {@link android.view.SurfaceHolder.Callback#surfaceCreated created}, 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(SurfaceHolder.class)} and then obtain the Surface by calling {@link
+ * android.view.SurfaceHolder#getSurface}. If the size is not set by the application, it will
+ * be rounded to the nearest supported size less than 1080p, by the camera device.</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>
+ * before creating a Surface from the SurfaceTexture with {@link Surface#Surface}. If the size
+ * is not set by the application, it will be set to be the smallest supported size less than
+ * 1080p, by the camera device.</li>
*
* <li>For recording with {@link android.media.MediaCodec}: Call
* {@link android.media.MediaCodec#createInputSurface} after configuring
@@ -189,6 +189,8 @@ public abstract class CameraDevice implements AutoCloseable {
* corresponding supported sizes by passing the chosen output format into
* {@link StreamConfigurationMap#getOutputSizes(int)}. Then obtain a
* {@link android.view.Surface} from it with {@link android.media.ImageReader#getSurface()}.
+ * If the ImageReader size is not set to a supported size, it will be rounded to a supported
+ * size less than 1080p by the camera device.
* </li>
*
* </ul>
@@ -334,6 +336,24 @@ public abstract class CameraDevice implements AutoCloseable {
* </table><br>
* </p>
*
+ * <p>BURST-capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} includes
+ * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE BURST_CAPTURE}) devices
+ * support at least the below stream combinations in addition to those for
+ * {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED} devices. Note that all
+ * FULL-level devices support the BURST capability, and the below list is a strict subset of the
+ * list for FULL-level devices, so this table is only relevant for LIMITED-level devices that
+ * support the BURST_CAPTURE capability.
+ *
+ * <table>
+ * <tr><th colspan="5">BURST-capability additional guaranteed configurations</th></tr>
+ * <tr><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th rowspan="2">Sample use case(s)</th> </tr>
+ * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th> </tr>
+ * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV}</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution GPU processing with preview.</td> </tr>
+ * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution in-app processing with preview.</td> </tr>
+ * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution two-input in-app processsing.</td> </tr>
+ * </table><br>
+ * </p>
+ *
* <p>Since the capabilities of camera devices vary greatly, a given camera device may support
* target combinations with sizes outside of these guarantees, but this can only be tested for
* by attempting to create a session with such targets.</p>
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 9a9e4c2..b6bb33b 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -54,29 +54,17 @@ public final class CameraManager {
private static final String TAG = "CameraManager";
private final boolean DEBUG;
- /**
- * This should match the ICameraService definition
- */
- private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
private static final int USE_CALLING_UID = -1;
@SuppressWarnings("unused")
private static final int API_VERSION_1 = 1;
private static final int API_VERSION_2 = 2;
- // Access only through getCameraServiceLocked to deal with binder death
- private ICameraService mCameraService;
-
private ArrayList<String> mDeviceIdList;
- private final ArrayMap<AvailabilityCallback, Handler> mCallbackMap =
- new ArrayMap<AvailabilityCallback, Handler>();
-
private final Context mContext;
private final Object mLock = new Object();
- private final CameraServiceListener mServiceListener = new CameraServiceListener();
-
/**
* @hide
*/
@@ -84,8 +72,6 @@ public final class CameraManager {
DEBUG = Log.isLoggable(TAG, Log.DEBUG);
synchronized(mLock) {
mContext = context;
-
- connectCameraServiceLocked();
}
}
@@ -116,6 +102,12 @@ public final class CameraManager {
* <p>The first time a callback is registered, it is immediately called
* with the availability status of all currently known camera devices.</p>
*
+ * <p>Since this callback will be registered with the camera service, remember to unregister it
+ * once it is no longer needed; otherwise the callback will continue to receive events
+ * indefinitely and it may prevent other resources from being released. Specifically, the
+ * callbacks will be invoked independently of the general activity lifecycle and independently
+ * of the state of individual CameraManager instances.</p>
+ *
* @param callback the new callback to send camera availability notices to
* @param handler The handler on which the callback should be invoked, or
* {@code null} to use the current thread's {@link android.os.Looper looper}.
@@ -130,13 +122,7 @@ public final class CameraManager {
handler = new Handler(looper);
}
- synchronized (mLock) {
- Handler oldHandler = mCallbackMap.put(callback, handler);
- // For new callbacks, provide initial availability information
- if (oldHandler == null) {
- mServiceListener.updateCallbackLocked(callback, handler);
- }
- }
+ CameraManagerGlobal.get().registerAvailabilityCallback(callback, handler);
}
/**
@@ -148,9 +134,7 @@ public final class CameraManager {
* @param callback The callback to remove from the notification list
*/
public void unregisterAvailabilityCallback(AvailabilityCallback callback) {
- synchronized (mLock) {
- mCallbackMap.remove(callback);
- }
+ CameraManagerGlobal.get().unregisterAvailabilityCallback(callback);
}
/**
@@ -187,7 +171,7 @@ public final class CameraManager {
* otherwise get them from the legacy shim instead.
*/
- ICameraService cameraService = getCameraServiceLocked();
+ ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
if (cameraService == null) {
throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
"Camera service is currently unavailable");
@@ -268,7 +252,7 @@ public final class CameraManager {
try {
if (supportsCamera2ApiLocked(cameraId)) {
// Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices
- ICameraService cameraService = getCameraServiceLocked();
+ ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
if (cameraService == null) {
throw new CameraRuntimeException(
CameraAccessException.CAMERA_DISCONNECTED,
@@ -444,13 +428,6 @@ public final class CameraManager {
}
/**
- * Temporary for migrating to Callback naming
- * @hide
- */
- public static abstract class AvailabilityListener extends AvailabilityCallback {
- }
-
- /**
* Return or create the list of currently connected camera devices.
*
* <p>In case of errors connecting to the camera service, will return an empty list.</p>
@@ -458,7 +435,7 @@ public final class CameraManager {
private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException {
if (mDeviceIdList == null) {
int numCameras = 0;
- ICameraService cameraService = getCameraServiceLocked();
+ ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
ArrayList<String> deviceIdList = new ArrayList<>();
// If no camera service, then no devices
@@ -515,18 +492,6 @@ 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());
- }
- }
-
/**
* Queries the camera service if it supports the camera2 api directly, or needs a shim.
*
@@ -556,7 +521,7 @@ public final class CameraManager {
* Anything else is an unexpected error we don't want to recover from.
*/
try {
- ICameraService cameraService = getCameraServiceLocked();
+ ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
// If no camera service, no support
if (cameraService == null) return false;
@@ -578,97 +543,23 @@ public final class CameraManager {
}
/**
- * Connect to the camera service if it's available, and set up listeners.
- *
- * <p>Sets mCameraService to a valid pointer or null if the connection does not succeed.</p>
+ * A per-process global camera manager instance, to retain a connection to the camera service,
+ * and to distribute camera availability notices to API-registered callbacks
*/
- private void connectCameraServiceLocked() {
- mCameraService = null;
- IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME);
- if (cameraServiceBinder == null) {
- // Camera service is now down, leave mCameraService as null
- return;
- }
- try {
- cameraServiceBinder.linkToDeath(new CameraServiceDeathListener(), /*flags*/ 0);
- } catch (RemoteException e) {
- // Camera service is now down, leave mCameraService as null
- return;
- }
+ private static final class CameraManagerGlobal extends ICameraServiceListener.Stub
+ implements IBinder.DeathRecipient {
+
+ private static final String TAG = "CameraManagerGlobal";
+ private final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder);
+ // Singleton instance
+ private static final CameraManagerGlobal gCameraManager =
+ new CameraManagerGlobal();
/**
- * Wrap the camera service in a decorator which automatically translates return codes
- * into exceptions.
+ * This must match the ICameraService definition
*/
- ICameraService cameraService = CameraServiceBinderDecorator.newInstance(cameraServiceRaw);
-
- try {
- CameraServiceBinderDecorator.throwOnError(
- CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor());
- } catch (CameraRuntimeException e) {
- handleRecoverableSetupErrors(e, "Failed to set up vendor tags");
- }
-
- try {
- cameraService.addListener(mServiceListener);
- mCameraService = cameraService;
- } catch(CameraRuntimeException e) {
- // Unexpected failure
- throw new IllegalStateException("Failed to register a camera service listener",
- e.asChecked());
- } catch (RemoteException e) {
- // Camera service is now down, leave mCameraService as null
- }
- }
-
- /**
- * Return a best-effort ICameraService.
- *
- * <p>This will be null if the camera service
- * is not currently available. If the camera service has died since the last
- * use of the camera service, will try to reconnect to the service.</p>
- */
- private ICameraService getCameraServiceLocked() {
- if (mCameraService == null) {
- Log.i(TAG, "getCameraServiceLocked: Reconnecting to camera service");
- connectCameraServiceLocked();
- if (mCameraService == null) {
- Log.e(TAG, "Camera service is unavailable");
- }
- }
- return mCameraService;
- }
-
- /**
- * Listener for camera service death.
- *
- * <p>The camera service isn't supposed to die under any normal circumstances, but can be turned
- * off during debug, or crash due to bugs. So detect that and null out the interface object, so
- * that the next calls to the manager can try to reconnect.</p>
- */
- private class CameraServiceDeathListener implements IBinder.DeathRecipient {
- public void binderDied() {
- synchronized(mLock) {
- mCameraService = null;
- // Tell listeners that the cameras are _available_, because any existing clients
- // will have gotten disconnected. This is optimistic under the assumption that the
- // service will be back shortly.
- //
- // Without this, a camera service crash while a camera is open will never signal to
- // listeners that previously in-use cameras are now available.
- for (String cameraId : mDeviceIdList) {
- mServiceListener.onStatusChangedLocked(CameraServiceListener.STATUS_PRESENT,
- cameraId);
- }
- }
- }
- }
-
- // TODO: this class needs unit tests
- // TODO: extract class into top level
- private class CameraServiceListener extends ICameraServiceListener.Stub {
+ private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
// Keep up-to-date with ICameraServiceListener.h
@@ -683,16 +574,112 @@ public final class CameraManager {
// Camera is in use by another app and cannot be used exclusively
public static final int STATUS_NOT_AVAILABLE = 0x80000000;
+ // End enums shared with ICameraServiceListener.h
+
// Camera ID -> Status map
private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>();
- private static final String TAG = "CameraServiceListener";
+ // Registered availablility callbacks and their handlers
+ private final ArrayMap<AvailabilityCallback, Handler> mCallbackMap =
+ new ArrayMap<AvailabilityCallback, Handler>();
+
+ private final Object mLock = new Object();
+
+ // Access only through getCameraService to deal with binder death
+ private ICameraService mCameraService;
+
+ // Singleton, don't allow construction
+ private CameraManagerGlobal() {
+ }
+
+ public static CameraManagerGlobal get() {
+ return gCameraManager;
+ }
@Override
public IBinder asBinder() {
return this;
}
+ /**
+ * Return a best-effort ICameraService.
+ *
+ * <p>This will be null if the camera service is not currently available. If the camera
+ * service has died since the last use of the camera service, will try to reconnect to the
+ * service.</p>
+ */
+ public ICameraService getCameraService() {
+ synchronized(mLock) {
+ if (mCameraService == null) {
+ Log.i(TAG, "getCameraService: Reconnecting to camera service");
+ connectCameraServiceLocked();
+ if (mCameraService == null) {
+ Log.e(TAG, "Camera service is unavailable");
+ }
+ }
+ return mCameraService;
+ }
+ }
+
+ /**
+ * Connect to the camera service if it's available, and set up listeners.
+ *
+ * <p>Sets mCameraService to a valid pointer or null if the connection does not succeed.</p>
+ */
+ private void connectCameraServiceLocked() {
+ mCameraService = null;
+ IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME);
+ if (cameraServiceBinder == null) {
+ // Camera service is now down, leave mCameraService as null
+ return;
+ }
+ try {
+ cameraServiceBinder.linkToDeath(this, /*flags*/ 0);
+ } catch (RemoteException e) {
+ // Camera service is now down, leave mCameraService as null
+ return;
+ }
+
+ ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder);
+
+ /**
+ * Wrap the camera service in a decorator which automatically translates return codes
+ * into exceptions.
+ */
+ ICameraService cameraService =
+ CameraServiceBinderDecorator.newInstance(cameraServiceRaw);
+
+ try {
+ CameraServiceBinderDecorator.throwOnError(
+ CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor());
+ } catch (CameraRuntimeException e) {
+ handleRecoverableSetupErrors(e, "Failed to set up vendor tags");
+ }
+
+ try {
+ cameraService.addListener(this);
+ mCameraService = cameraService;
+ } catch(CameraRuntimeException e) {
+ // Unexpected failure
+ throw new IllegalStateException("Failed to register a camera service listener",
+ e.asChecked());
+ } catch (RemoteException e) {
+ // Camera service is now down, leave mCameraService as null
+ }
+ }
+
+ 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());
+ }
+ }
+
private boolean isAvailable(int status) {
switch (status) {
case STATUS_PRESENT:
@@ -739,7 +726,7 @@ public final class CameraManager {
* Send the state of all known cameras to the provided listener, to initialize
* the listener's knowledge of camera state.
*/
- public void updateCallbackLocked(AvailabilityCallback callback, Handler handler) {
+ private void updateCallbackLocked(AvailabilityCallback callback, Handler handler) {
for (int i = 0; i < mDeviceStatus.size(); i++) {
String id = mDeviceStatus.keyAt(i);
Integer status = mDeviceStatus.valueAt(i);
@@ -747,14 +734,7 @@ public final class CameraManager {
}
}
- @Override
- public void onStatusChanged(int status, int cameraId) throws RemoteException {
- synchronized(CameraManager.this.mLock) {
- onStatusChangedLocked(status, String.valueOf(cameraId));
- }
- }
-
- public void onStatusChangedLocked(int status, String id) {
+ private void onStatusChangedLocked(int status, String id) {
if (DEBUG) {
Log.v(TAG,
String.format("Camera id %s has status changed to 0x%x", id, status));
@@ -811,5 +791,72 @@ public final class CameraManager {
}
} // onStatusChangedLocked
- } // CameraServiceListener
+ /**
+ * Register a callback to be notified about camera device availability with the
+ * global listener singleton.
+ *
+ * @param callback the new callback to send camera availability notices to
+ * @param handler The handler on which the callback should be invoked. May not be null.
+ */
+ public void registerAvailabilityCallback(AvailabilityCallback callback, Handler handler) {
+ synchronized (mLock) {
+ Handler oldHandler = mCallbackMap.put(callback, handler);
+ // For new callbacks, provide initial availability information
+ if (oldHandler == null) {
+ updateCallbackLocked(callback, handler);
+ }
+ }
+ }
+
+ /**
+ * Remove a previously-added callback; the callback will no longer receive connection and
+ * disconnection callbacks, and is no longer referenced by the global listener singleton.
+ *
+ * @param callback The callback to remove from the notification list
+ */
+ public void unregisterAvailabilityCallback(AvailabilityCallback callback) {
+ synchronized (mLock) {
+ mCallbackMap.remove(callback);
+ }
+ }
+
+ /**
+ * Callback from camera service notifying the process about camera availability changes
+ */
+ @Override
+ public void onStatusChanged(int status, int cameraId) throws RemoteException {
+ synchronized(mLock) {
+ onStatusChangedLocked(status, String.valueOf(cameraId));
+ }
+ }
+
+ /**
+ * Listener for camera service death.
+ *
+ * <p>The camera service isn't supposed to die under any normal circumstances, but can be
+ * turned off during debug, or crash due to bugs. So detect that and null out the interface
+ * object, so that the next calls to the manager can try to reconnect.</p>
+ */
+ public void binderDied() {
+ synchronized(mLock) {
+ // Only do this once per service death
+ if (mCameraService == null) return;
+
+ mCameraService = null;
+
+ // Tell listeners that the cameras are _available_, because any existing clients
+ // will have gotten disconnected. This is optimistic under the assumption that
+ // the service will be back shortly.
+ //
+ // Without this, a camera service crash while a camera is open will never signal
+ // to listeners that previously in-use cameras are now available.
+ for (int i = 0; i < mDeviceStatus.size(); i++) {
+ String cameraId = mDeviceStatus.keyAt(i);
+ onStatusChangedLocked(STATUS_PRESENT, cameraId);
+ }
+ }
+ }
+
+ } // CameraManagerGlobal
+
} // CameraManager
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 16df844..1b10858 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -450,6 +450,64 @@ public abstract class CameraMetadata<TKey> {
*/
public static final int REQUEST_AVAILABLE_CAPABILITIES_ZSL = 4;
+ /**
+ * <p>The camera device supports accurately reporting the sensor settings for many of
+ * the sensor controls while the built-in 3A algorithm is running. This allows
+ * reporting of sensor settings even when these settings cannot be manually changed.</p>
+ * <p>The values reported for the following controls are guaranteed to be available
+ * in the CaptureResult, including when 3A is enabled:</p>
+ * <ul>
+ * <li>Exposure control<ul>
+ * <li>{@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}</li>
+ * </ul>
+ * </li>
+ * <li>Sensitivity control<ul>
+ * <li>{@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}</li>
+ * </ul>
+ * </li>
+ * <li>Lens controls (if the lens is adjustable)<ul>
+ * <li>{@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance}</li>
+ * <li>{@link CaptureRequest#LENS_APERTURE android.lens.aperture}</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * <p>This capability is a subset of the MANUAL_SENSOR control capability, and will
+ * always be included if the MANUAL_SENSOR capability is available.</p>
+ *
+ * @see CaptureRequest#LENS_APERTURE
+ * @see CaptureRequest#LENS_FOCUS_DISTANCE
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CaptureRequest#SENSOR_SENSITIVITY
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS = 5;
+
+ /**
+ * <p>The camera device supports capturing maximum-resolution
+ * images at &gt;= 20 frames per second, in at least the
+ * uncompressed YUV format, when post-processing settings
+ * are set to FAST.</p>
+ * <p>More specifically, this means that a size matching the
+ * camera device's active array size is listed as a
+ * supported size for the YUV_420_888 format in
+ * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}, the minimum frame
+ * duration for that format and size is &lt;= 1/20 s, and
+ * the {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges} entry
+ * lists at least one FPS range where the minimum FPS is</p>
+ * <blockquote>
+ * <p>= 1 / minimumFrameDuration for the maximum-size
+ * YUV_420_888 format.</p>
+ * </blockquote>
+ * <p>In addition, the {@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} field is
+ * guaranted to have a value between 0 and 4, inclusive.</p>
+ *
+ * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
+ * @see CameraCharacteristics#SYNC_MAX_LATENCY
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE = 6;
+
//
// Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE
//
@@ -791,7 +849,8 @@ public abstract class CameraMetadata<TKey> {
/**
* <p>The camera device will automatically adapt its
* antibanding routine to the current illumination
- * conditions. This is the default.</p>
+ * condition. This is the default mode if AUTO is
+ * available on given camera device.</p>
* @see CaptureRequest#CONTROL_AE_ANTIBANDING_MODE
*/
public static final int CONTROL_AE_ANTIBANDING_MODE_AUTO = 3;
@@ -807,9 +866,21 @@ public abstract class CameraMetadata<TKey> {
* {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} are used by the camera
* device, along with android.flash.* fields, if there's
* a flash unit for this camera device.</p>
+ * <p>Note that auto-white balance (AWB) and auto-focus (AF)
+ * behavior is device dependent when AE is in OFF mode.
+ * To have consistent behavior across different devices,
+ * it is recommended to either set AWB and AF to OFF mode
+ * or lock AWB and AF before setting AE to OFF.
+ * See {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}, {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode},
+ * {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock}, and {@link CaptureRequest#CONTROL_AF_TRIGGER android.control.afTrigger}
+ * for more details.</p>
* <p>LEGACY devices do not support the OFF mode and will
* override attempts to use this value to ON.</p>
*
+ * @see CaptureRequest#CONTROL_AF_MODE
+ * @see CaptureRequest#CONTROL_AF_TRIGGER
+ * @see CaptureRequest#CONTROL_AWB_LOCK
+ * @see CaptureRequest#CONTROL_AWB_MODE
* @see CaptureRequest#SENSOR_EXPOSURE_TIME
* @see CaptureRequest#SENSOR_FRAME_DURATION
* @see CaptureRequest#SENSOR_SENSITIVITY
@@ -1577,11 +1648,41 @@ public abstract class CameraMetadata<TKey> {
public static final int CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO = 17;
/**
- * <p>Turn on custom high dynamic range (HDR) mode.</p>
- * <p>This is intended for LEGACY mode devices only;
- * HAL3+ camera devices should not implement this mode.</p>
+ * <p>Turn on a device-specific high dynamic range (HDR) mode.</p>
+ * <p>In this scene mode, the camera device captures images
+ * that keep a larger range of scene illumination levels
+ * visible in the final image. For example, when taking a
+ * picture of a object in front of a bright window, both
+ * the object and the scene through the window may be
+ * visible when using HDR mode, while in normal AUTO mode,
+ * one or the other may be poorly exposed. As a tradeoff,
+ * HDR mode generally takes much longer to capture a single
+ * image, has no user control, and may have other artifacts
+ * depending on the HDR method used.</p>
+ * <p>Therefore, HDR captures operate at a much slower rate
+ * than regular captures.</p>
+ * <p>In this mode, on LIMITED or FULL devices, when a request
+ * is made with a {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} of
+ * STILL_CAPTURE, the camera device will capture an image
+ * using a high dynamic range capture technique. On LEGACY
+ * devices, captures that target a JPEG-format output will
+ * be captured with HDR, and the capture intent is not
+ * relevant.</p>
+ * <p>The HDR capture may involve the device capturing a burst
+ * of images internally and combining them into one, or it
+ * may involve the device using specialized high dynamic
+ * range capture hardware. In all cases, a single image is
+ * produced in response to a capture request submitted
+ * while in HDR mode.</p>
+ * <p>Since substantial post-processing is generally needed to
+ * produce an HDR image, only YUV and JPEG outputs are
+ * supported for LIMITED/FULL device HDR captures, and only
+ * JPEG outputs are supported for LEGACY HDR
+ * captures. Using a RAW output for HDR capture is not
+ * supported.</p>
+ *
+ * @see CaptureRequest#CONTROL_CAPTURE_INTENT
* @see CaptureRequest#CONTROL_SCENE_MODE
- * @hide
*/
public static final int CONTROL_SCENE_MODE_HDR = 18;
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 6aec72a..b417496 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -642,8 +642,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* options for the antibanding mode. The
* {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_ANTIBANDING_MODES android.control.aeAvailableAntibandingModes} key contains
* the available modes for a given camera device.</p>
- * <p>The default mode is AUTO, which is supported by all
- * camera devices.</p>
+ * <p>AUTO mode is the default if it is available on given
+ * camera device. When AUTO mode is not available, the
+ * default will be either 50HZ or 60HZ, and both 50HZ
+ * and 60HZ will be available.</p>
* <p>If manual exposure control is enabled (by setting
* {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} to OFF),
* then this setting has no effect, and the application must
@@ -865,9 +867,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* metering sequence when it processes this request.</p>
* <p>This entry is normally set to IDLE, or is not
* included at all in the request settings. When included and
- * set to START, the camera device will trigger the autoexposure
+ * set to START, the camera device will trigger the auto-exposure (AE)
* precapture metering sequence.</p>
- * <p>The precapture sequence should triggered before starting a
+ * <p>The precapture sequence should be triggered before starting a
* high-quality still capture for final metering decisions to
* be made, and for firing pre-capture flash pulses to estimate
* scene brightness and required final capture flash power, when
@@ -875,6 +877,14 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <p>Normally, this entry should be set to START for only a
* single request, and the application should wait until the
* sequence completes before starting a new one.</p>
+ * <p>When a precapture metering sequence is finished, the camera device
+ * may lock the auto-exposure routine internally to be able to accurately expose the
+ * subsequent still capture image (<code>{@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} == STILL_CAPTURE</code>).
+ * For this case, the AE may not resume normal scan if no subsequent still capture is
+ * submitted. To ensure that the AE routine restarts normal scan, the application should
+ * submit a request with <code>{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} == true</code>, followed by a request
+ * with <code>{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} == false</code>, if the application decides not to submit a
+ * still capture request after the precapture sequence completes.</p>
* <p>The exact effect of auto-exposure (AE) precapture trigger
* depends on the current AE mode and state; see
* {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} for AE precapture state transition
@@ -893,7 +903,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
+ * @see CaptureRequest#CONTROL_AE_LOCK
* @see CaptureResult#CONTROL_AE_STATE
+ * @see CaptureRequest#CONTROL_CAPTURE_INTENT
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see #CONTROL_AE_PRECAPTURE_TRIGGER_IDLE
* @see #CONTROL_AE_PRECAPTURE_TRIGGER_START
@@ -906,7 +918,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <p>Whether auto-focus (AF) is currently enabled, and what
* mode it is set to.</p>
* <p>Only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} = AUTO and the lens is not fixed focus
- * (i.e. <code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} &gt; 0</code>).</p>
+ * (i.e. <code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} &gt; 0</code>). Also note that
+ * when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is OFF, the behavior of AF is device
+ * dependent. It is recommended to lock AF by using {@link CaptureRequest#CONTROL_AF_TRIGGER android.control.afTrigger} before
+ * setting {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} to OFF, or set AF mode to OFF when AE is OFF.</p>
* <p>If the lens is controlled by the camera device auto-focus algorithm,
* the camera device will report the current AF status in {@link CaptureResult#CONTROL_AF_STATE android.control.afState}
* in result metadata.</p>
@@ -923,8 +938,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* {@link CameraCharacteristics#CONTROL_AF_AVAILABLE_MODES android.control.afAvailableModes}</p>
* <p>This key is available on all devices.</p>
*
+ * @see CaptureRequest#CONTROL_AE_MODE
* @see CameraCharacteristics#CONTROL_AF_AVAILABLE_MODES
* @see CaptureResult#CONTROL_AF_STATE
+ * @see CaptureRequest#CONTROL_AF_TRIGGER
* @see CaptureRequest#CONTROL_MODE
* @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE
* @see #CONTROL_AF_MODE_OFF
@@ -1046,7 +1063,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <p>When set to the ON mode, the camera device's auto-white balance
* routine is enabled, overriding the application's selected
* {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and
- * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}.</p>
+ * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}. Note that when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}
+ * is OFF, the behavior of AWB is device dependent. It is recommened to
+ * also set AWB mode to OFF or lock AWB by using {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} before
+ * setting AE mode to OFF.</p>
* <p>When set to the OFF mode, the camera device's auto-white balance
* routine is disabled. The application manually controls the white
* balance by {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains}
@@ -1077,7 +1097,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @see CaptureRequest#COLOR_CORRECTION_GAINS
* @see CaptureRequest#COLOR_CORRECTION_MODE
* @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
+ * @see CaptureRequest#CONTROL_AE_MODE
* @see CameraCharacteristics#CONTROL_AWB_AVAILABLE_MODES
+ * @see CaptureRequest#CONTROL_AWB_LOCK
* @see CaptureRequest#CONTROL_MODE
* @see #CONTROL_AWB_MODE_OFF
* @see #CONTROL_AWB_MODE_AUTO
@@ -1282,6 +1304,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <li>{@link #CONTROL_SCENE_MODE_CANDLELIGHT CANDLELIGHT}</li>
* <li>{@link #CONTROL_SCENE_MODE_BARCODE BARCODE}</li>
* <li>{@link #CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO HIGH_SPEED_VIDEO}</li>
+ * <li>{@link #CONTROL_SCENE_MODE_HDR HDR}</li>
* </ul></p>
* <p><b>Available values for this device:</b><br>
* {@link CameraCharacteristics#CONTROL_AVAILABLE_SCENE_MODES android.control.availableSceneModes}</p>
@@ -1310,6 +1333,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @see #CONTROL_SCENE_MODE_CANDLELIGHT
* @see #CONTROL_SCENE_MODE_BARCODE
* @see #CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO
+ * @see #CONTROL_SCENE_MODE_HDR
*/
@PublicKey
public static final Key<Integer> CONTROL_SCENE_MODE =
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index d208649..f17100d 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -493,8 +493,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* options for the antibanding mode. The
* {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_ANTIBANDING_MODES android.control.aeAvailableAntibandingModes} key contains
* the available modes for a given camera device.</p>
- * <p>The default mode is AUTO, which is supported by all
- * camera devices.</p>
+ * <p>AUTO mode is the default if it is available on given
+ * camera device. When AUTO mode is not available, the
+ * default will be either 50HZ or 60HZ, and both 50HZ
+ * and 60HZ will be available.</p>
* <p>If manual exposure control is enabled (by setting
* {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} to OFF),
* then this setting has no effect, and the application must
@@ -716,9 +718,9 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* metering sequence when it processes this request.</p>
* <p>This entry is normally set to IDLE, or is not
* included at all in the request settings. When included and
- * set to START, the camera device will trigger the autoexposure
+ * set to START, the camera device will trigger the auto-exposure (AE)
* precapture metering sequence.</p>
- * <p>The precapture sequence should triggered before starting a
+ * <p>The precapture sequence should be triggered before starting a
* high-quality still capture for final metering decisions to
* be made, and for firing pre-capture flash pulses to estimate
* scene brightness and required final capture flash power, when
@@ -726,6 +728,14 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>Normally, this entry should be set to START for only a
* single request, and the application should wait until the
* sequence completes before starting a new one.</p>
+ * <p>When a precapture metering sequence is finished, the camera device
+ * may lock the auto-exposure routine internally to be able to accurately expose the
+ * subsequent still capture image (<code>{@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} == STILL_CAPTURE</code>).
+ * For this case, the AE may not resume normal scan if no subsequent still capture is
+ * submitted. To ensure that the AE routine restarts normal scan, the application should
+ * submit a request with <code>{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} == true</code>, followed by a request
+ * with <code>{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} == false</code>, if the application decides not to submit a
+ * still capture request after the precapture sequence completes.</p>
* <p>The exact effect of auto-exposure (AE) precapture trigger
* depends on the current AE mode and state; see
* {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} for AE precapture state transition
@@ -744,7 +754,9 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
+ * @see CaptureRequest#CONTROL_AE_LOCK
* @see CaptureResult#CONTROL_AE_STATE
+ * @see CaptureRequest#CONTROL_CAPTURE_INTENT
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see #CONTROL_AE_PRECAPTURE_TRIGGER_IDLE
* @see #CONTROL_AE_PRECAPTURE_TRIGGER_START
@@ -970,7 +982,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>Whether auto-focus (AF) is currently enabled, and what
* mode it is set to.</p>
* <p>Only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} = AUTO and the lens is not fixed focus
- * (i.e. <code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} &gt; 0</code>).</p>
+ * (i.e. <code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} &gt; 0</code>). Also note that
+ * when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is OFF, the behavior of AF is device
+ * dependent. It is recommended to lock AF by using {@link CaptureRequest#CONTROL_AF_TRIGGER android.control.afTrigger} before
+ * setting {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} to OFF, or set AF mode to OFF when AE is OFF.</p>
* <p>If the lens is controlled by the camera device auto-focus algorithm,
* the camera device will report the current AF status in {@link CaptureResult#CONTROL_AF_STATE android.control.afState}
* in result metadata.</p>
@@ -987,8 +1002,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* {@link CameraCharacteristics#CONTROL_AF_AVAILABLE_MODES android.control.afAvailableModes}</p>
* <p>This key is available on all devices.</p>
*
+ * @see CaptureRequest#CONTROL_AE_MODE
* @see CameraCharacteristics#CONTROL_AF_AVAILABLE_MODES
* @see CaptureResult#CONTROL_AF_STATE
+ * @see CaptureRequest#CONTROL_AF_TRIGGER
* @see CaptureRequest#CONTROL_MODE
* @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE
* @see #CONTROL_AF_MODE_OFF
@@ -1519,7 +1536,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>When set to the ON mode, the camera device's auto-white balance
* routine is enabled, overriding the application's selected
* {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and
- * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}.</p>
+ * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}. Note that when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}
+ * is OFF, the behavior of AWB is device dependent. It is recommened to
+ * also set AWB mode to OFF or lock AWB by using {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} before
+ * setting AE mode to OFF.</p>
* <p>When set to the OFF mode, the camera device's auto-white balance
* routine is disabled. The application manually controls the white
* balance by {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains}
@@ -1550,7 +1570,9 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @see CaptureRequest#COLOR_CORRECTION_GAINS
* @see CaptureRequest#COLOR_CORRECTION_MODE
* @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
+ * @see CaptureRequest#CONTROL_AE_MODE
* @see CameraCharacteristics#CONTROL_AWB_AVAILABLE_MODES
+ * @see CaptureRequest#CONTROL_AWB_LOCK
* @see CaptureRequest#CONTROL_MODE
* @see #CONTROL_AWB_MODE_OFF
* @see #CONTROL_AWB_MODE_AUTO
@@ -1898,6 +1920,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <li>{@link #CONTROL_SCENE_MODE_CANDLELIGHT CANDLELIGHT}</li>
* <li>{@link #CONTROL_SCENE_MODE_BARCODE BARCODE}</li>
* <li>{@link #CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO HIGH_SPEED_VIDEO}</li>
+ * <li>{@link #CONTROL_SCENE_MODE_HDR HDR}</li>
* </ul></p>
* <p><b>Available values for this device:</b><br>
* {@link CameraCharacteristics#CONTROL_AVAILABLE_SCENE_MODES android.control.availableSceneModes}</p>
@@ -1926,6 +1949,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @see #CONTROL_SCENE_MODE_CANDLELIGHT
* @see #CONTROL_SCENE_MODE_BARCODE
* @see #CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO
+ * @see #CONTROL_SCENE_MODE_HDR
*/
@PublicKey
public static final Key<Integer> CONTROL_SCENE_MODE =
diff --git a/core/java/android/hardware/camera2/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java
index 6fc99ac..33d539c 100644
--- a/core/java/android/hardware/camera2/DngCreator.java
+++ b/core/java/android/hardware/camera2/DngCreator.java
@@ -530,9 +530,9 @@ public final class DngCreator implements AutoCloseable {
int uPixStride = uPlane.getPixelStride();
byte[] yuvPixel = { 0, 0, 0 };
- byte[] yFullRow = new byte[yPixStride * width];
- byte[] uFullRow = new byte[uPixStride * width / 2];
- byte[] vFullRow = new byte[vPixStride * width / 2];
+ byte[] yFullRow = new byte[yPixStride * (width - 1) + 1];
+ byte[] uFullRow = new byte[uPixStride * (width / 2 - 1) + 1];
+ byte[] vFullRow = new byte[vPixStride * (width / 2 - 1) + 1];
byte[] finalRow = new byte[BYTES_PER_RGB_PIX * width];
for (int i = 0; i < height; i++) {
int halfH = i / 2;
diff --git a/core/java/android/hardware/camera2/legacy/GLThreadManager.java b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
index 64c532b..b160d2a 100644
--- a/core/java/android/hardware/camera2/legacy/GLThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
@@ -22,6 +22,8 @@ import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
+import android.util.Pair;
+import android.util.Size;
import android.view.Surface;
import java.util.Collection;
@@ -57,11 +59,11 @@ public class GLThreadManager {
*/
private static class ConfigureHolder {
public final ConditionVariable condition;
- public final Collection<Surface> surfaces;
+ public final Collection<Pair<Surface, Size>> surfaces;
public final CaptureCollector collector;
- public ConfigureHolder(ConditionVariable condition, Collection<Surface> surfaces,
- CaptureCollector collector) {
+ public ConfigureHolder(ConditionVariable condition, Collection<Pair<Surface,
+ Size>> surfaces, CaptureCollector collector) {
this.condition = condition;
this.surfaces = surfaces;
this.collector = collector;
@@ -202,10 +204,12 @@ public class GLThreadManager {
* 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.
+ * @param surfaces a collection of pairs of {@link android.view.Surface}s and their
+ * corresponding sizes to configure.
* @param collector a {@link CaptureCollector} to retrieve requests from.
*/
- public void setConfigurationAndWait(Collection<Surface> surfaces, CaptureCollector collector) {
+ public void setConfigurationAndWait(Collection<Pair<Surface, Size>> surfaces,
+ CaptureCollector collector) {
checkNotNull(collector, "collector must not be null");
Handler handler = mGLHandlerThread.getHandler();
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index 3a976ba..367a078 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -24,7 +24,6 @@ import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.impl.CameraDeviceImpl;
import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.ICameraDeviceCallbacks;
-import android.hardware.camera2.params.StreamConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.utils.ArrayUtils;
import android.hardware.camera2.utils.CameraBinderDecorator;
@@ -36,6 +35,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Pair;
import android.util.Size;
import android.view.Surface;
@@ -78,6 +78,15 @@ public class LegacyCameraDevice implements AutoCloseable {
private final Handler mResultHandler;
private static final int ILLEGAL_VALUE = -1;
+ // Keep up to date with values in hardware/libhardware/include/hardware/gralloc.h
+ private static final int GRALLOC_USAGE_RENDERSCRIPT = 0x00100000;
+ private static final int GRALLOC_USAGE_SW_READ_OFTEN = 0x00000003;
+ private static final int GRALLOC_USAGE_HW_TEXTURE = 0x00000100;
+ private static final int GRALLOC_USAGE_HW_COMPOSER = 0x00000800;
+ private static final int GRALLOC_USAGE_HW_VIDEO_ENCODER = 0x00010000;
+
+ public static final int MAX_DIMEN_FOR_ROUNDING = 1080; // maximum allowed width for rounding
+
private CaptureResultExtras getExtrasFromRequest(RequestHolder holder) {
if (holder == null) {
return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE,
@@ -276,6 +285,7 @@ public class LegacyCameraDevice implements AutoCloseable {
* on success.
*/
public int configureOutputs(List<Surface> outputs) {
+ List<Pair<Surface, Size>> sizedSurfaces = new ArrayList<>();
if (outputs != null) {
for (Surface output : outputs) {
if (output == null) {
@@ -289,16 +299,18 @@ public class LegacyCameraDevice implements AutoCloseable {
try {
Size s = getSurfaceSize(output);
int surfaceType = detectSurfaceType(output);
- Size[] sizes = streamConfigurations.getOutputSizes(surfaceType);
+ boolean flexibleConsumer = isFlexibleConsumer(output);
+
+ Size[] sizes = streamConfigurations.getOutputSizes(surfaceType);
if (sizes == null) {
// WAR: Override default format to IMPLEMENTATION_DEFINED for b/9487482
if ((surfaceType >= LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888 &&
surfaceType <= LegacyMetadataMapper.HAL_PIXEL_FORMAT_BGRA_8888)) {
- // YUV_420_888 is always present in LEGACY for all IMPLEMENTATION_DEFINED
- // output sizes, and is publicly visible in the API (i.e.
- // {@code #getOutputSizes} works here).
+ // YUV_420_888 is always present in LEGACY for all
+ // IMPLEMENTATION_DEFINED output sizes, and is publicly visible in the
+ // API (i.e. {@code #getOutputSizes} works here).
sizes = streamConfigurations.getOutputSizes(ImageFormat.YUV_420_888);
} else if (surfaceType == LegacyMetadataMapper.HAL_PIXEL_FORMAT_BLOB) {
sizes = streamConfigurations.getOutputSizes(ImageFormat.JPEG);
@@ -306,12 +318,18 @@ public class LegacyCameraDevice implements AutoCloseable {
}
if (!ArrayUtils.contains(sizes, s)) {
- String reason = (sizes == null) ? "format is invalid." :
- ("size not in valid set: " + Arrays.toString(sizes));
- Log.e(TAG, String.format("Surface with size (w=%d, h=%d) and format 0x%x is"
- + " not valid, %s", s.getWidth(), s.getHeight(), surfaceType,
- reason));
- return BAD_VALUE;
+ if (flexibleConsumer && (s = findClosestSize(s, sizes)) != null) {
+ sizedSurfaces.add(new Pair<>(output, s));
+ } else {
+ String reason = (sizes == null) ? "format is invalid." :
+ ("size not in valid set: " + Arrays.toString(sizes));
+ Log.e(TAG, String.format("Surface with size (w=%d, h=%d) and format " +
+ "0x%x is not valid, %s", s.getWidth(), s.getHeight(),
+ surfaceType, reason));
+ return BAD_VALUE;
+ }
+ } else {
+ sizedSurfaces.add(new Pair<>(output, s));
}
} catch (BufferQueueAbandonedException e) {
Log.e(TAG, "Surface bufferqueue is abandoned, cannot configure as output: ", e);
@@ -323,7 +341,7 @@ public class LegacyCameraDevice implements AutoCloseable {
boolean success = false;
if (mDeviceState.setConfiguring()) {
- mRequestThreadManager.configure(outputs);
+ mRequestThreadManager.configure(sizedSurfaces);
success = mDeviceState.setIdle();
}
@@ -473,6 +491,31 @@ public class LegacyCameraDevice implements AutoCloseable {
}
}
+ static long findEuclidDistSquare(Size a, Size b) {
+ long d0 = a.getWidth() - b.getWidth();
+ long d1 = a.getHeight() - b.getHeight();
+ return d0 * d0 + d1 * d1;
+ }
+
+ // Keep up to date with rounding behavior in
+ // frameworks/av/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+ static Size findClosestSize(Size size, Size[] supportedSizes) {
+ if (size == null || supportedSizes == null) {
+ return null;
+ }
+ Size bestSize = null;
+ for (Size s : supportedSizes) {
+ if (s.equals(size)) {
+ return size;
+ } else if (s.getWidth() <= MAX_DIMEN_FOR_ROUNDING && (bestSize == null ||
+ LegacyCameraDevice.findEuclidDistSquare(size, s) <
+ LegacyCameraDevice.findEuclidDistSquare(bestSize, s))) {
+ bestSize = s;
+ }
+ }
+ return bestSize;
+ }
+
/**
* Query the surface for its currently configured default buffer size.
* @param surface a non-{@code null} {@code Surface}
@@ -481,7 +524,7 @@ public class LegacyCameraDevice implements AutoCloseable {
* @throws NullPointerException if the {@code surface} was {@code null}
* @throws IllegalStateException if the {@code surface} was invalid
*/
- static Size getSurfaceSize(Surface surface) throws BufferQueueAbandonedException {
+ public static Size getSurfaceSize(Surface surface) throws BufferQueueAbandonedException {
checkNotNull(surface);
int[] dimens = new int[2];
@@ -490,7 +533,31 @@ public class LegacyCameraDevice implements AutoCloseable {
return new Size(dimens[0], dimens[1]);
}
- static int detectSurfaceType(Surface surface) throws BufferQueueAbandonedException {
+ public static boolean isFlexibleConsumer(Surface output) {
+ int usageFlags = detectSurfaceUsageFlags(output);
+
+ // Keep up to date with allowed consumer types in
+ // frameworks/av/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+ int disallowedFlags = GRALLOC_USAGE_HW_VIDEO_ENCODER | GRALLOC_USAGE_RENDERSCRIPT;
+ int allowedFlags = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_SW_READ_OFTEN |
+ GRALLOC_USAGE_HW_COMPOSER;
+ boolean flexibleConsumer = ((usageFlags & disallowedFlags) == 0 &&
+ (usageFlags & allowedFlags) != 0);
+ return flexibleConsumer;
+ }
+
+ /**
+ * Query the surface for its currently configured usage flags
+ */
+ static int detectSurfaceUsageFlags(Surface surface) {
+ checkNotNull(surface);
+ return nativeDetectSurfaceUsageFlags(surface);
+ }
+
+ /**
+ * Query the surface for its currently configured format
+ */
+ public static int detectSurfaceType(Surface surface) throws BufferQueueAbandonedException {
checkNotNull(surface);
return LegacyExceptionUtils.throwOnError(nativeDetectSurfaceType(surface));
}
@@ -608,5 +675,7 @@ public class LegacyCameraDevice implements AutoCloseable {
private static native int nativeSetNextTimestamp(Surface surface, long timestamp);
+ private static native int nativeDetectSurfaceUsageFlags(Surface surface);
+
static native int nativeGetJpegFooterSize();
}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index 03540e1..347db05 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -328,15 +328,15 @@ public class LegacyMetadataMapper {
appendStreamConfig(availableStreamConfigs,
ImageFormat.YUV_420_888, previewSizes);
for (int format : p.getSupportedPreviewFormats()) {
- if (ImageFormat.isPublicFormat(format)) {
+ if (ImageFormat.isPublicFormat(format) && format != ImageFormat.NV21) {
appendStreamConfig(availableStreamConfigs, format, previewSizes);
- } else {
+ } else if (VERBOSE) {
/*
* Do not add any formats unknown to us
* (since it would fail runtime checks in StreamConfigurationMap)
*/
- Log.w(TAG,
- String.format("mapStreamConfigs - Skipping non-public format %x", format));
+ Log.v(TAG,
+ String.format("mapStreamConfigs - Skipping format %x", format));
}
}
@@ -389,8 +389,8 @@ public class LegacyMetadataMapper {
int j = 0;
for (String mode : antiBandingModes) {
int convertedMode = convertAntiBandingMode(mode);
- if (convertedMode == -1) {
- Log.w(TAG, "Antibanding mode " + ((mode == null) ? "NULL" : mode) +
+ if (VERBOSE && convertedMode == -1) {
+ Log.v(TAG, "Antibanding mode " + ((mode == null) ? "NULL" : mode) +
" not supported, skipping...");
} else {
modes[j++] = convertedMode;
diff --git a/core/java/android/hardware/camera2/legacy/ParameterUtils.java b/core/java/android/hardware/camera2/legacy/ParameterUtils.java
index 98adcea..3b10eb5 100644
--- a/core/java/android/hardware/camera2/legacy/ParameterUtils.java
+++ b/core/java/android/hardware/camera2/legacy/ParameterUtils.java
@@ -918,7 +918,9 @@ public class ParameterUtils {
convertCameraAreaToActiveArrayRectangle(activeArray, zoomData, fakeArea);
Point leftEye = face.leftEye, rightEye = face.rightEye, mouth = face.mouth;
- if (leftEye != null && rightEye != null && mouth != null) {
+ if (leftEye != null && rightEye != null && mouth != null && leftEye.x != -2000 &&
+ leftEye.y != -2000 && rightEye.x != -2000 && rightEye.y != -2000 &&
+ mouth.x != -2000 && mouth.y != -2000) {
leftEye = convertCameraPointToActiveArrayPoint(activeArray, zoomData,
leftEye, /*usePreviewCrop*/true);
rightEye = convertCameraPointToActiveArrayPoint(activeArray, zoomData,
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index 35deb71..f1f2f0c 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -41,8 +41,10 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import static com.android.internal.util.Preconditions.*;
@@ -67,7 +69,7 @@ public class RequestThreadManager {
// For slightly more spammy messages that will get repeated every frame
private static final boolean VERBOSE =
Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.VERBOSE);
- private final Camera mCamera;
+ private Camera mCamera;
private final CameraCharacteristics mCharacteristics;
private final CameraDeviceState mDeviceState;
@@ -82,8 +84,8 @@ public class RequestThreadManager {
private static final int MAX_IN_FLIGHT_REQUESTS = 2;
private static final int PREVIEW_FRAME_TIMEOUT = 1000; // ms
- private static final int JPEG_FRAME_TIMEOUT = 3000; // ms (same as CTS for API2)
- private static final int REQUEST_COMPLETE_TIMEOUT = 3000; // ms (same as JPEG timeout)
+ private static final int JPEG_FRAME_TIMEOUT = 4000; // ms (same as CTS for API2)
+ private static final int REQUEST_COMPLETE_TIMEOUT = JPEG_FRAME_TIMEOUT; // ms (same as JPEG timeout)
private static final float ASPECT_RATIO_TOLERANCE = 0.01f;
private boolean mPreviewRunning = false;
@@ -107,6 +109,8 @@ public class RequestThreadManager {
private final FpsCounter mPrevCounter = new FpsCounter("Incoming Preview");
private final FpsCounter mRequestCounter = new FpsCounter("Incoming Requests");
+ private final AtomicBoolean mQuit = new AtomicBoolean(false);
+
// Stuff JPEGs into HAL_PIXEL_FORMAT_RGBA_8888 gralloc buffers to get around SW write
// limitations for (b/17379185).
private static final boolean USE_BLOB_FORMAT_OVERRIDE = true;
@@ -116,9 +120,10 @@ public class RequestThreadManager {
*/
private static class ConfigureHolder {
public final ConditionVariable condition;
- public final Collection<Surface> surfaces;
+ public final Collection<Pair<Surface, Size>> surfaces;
- public ConfigureHolder(ConditionVariable condition, Collection<Surface> surfaces) {
+ public ConfigureHolder(ConditionVariable condition, Collection<Pair<Surface,
+ Size>> surfaces) {
this.condition = condition;
this.surfaces = surfaces;
}
@@ -317,13 +322,21 @@ public class RequestThreadManager {
startPreview();
}
- private void configureOutputs(Collection<Surface> outputs) {
+ private void configureOutputs(Collection<Pair<Surface, Size>> outputs) {
if (DEBUG) {
String outputsStr = outputs == null ? "null" : (outputs.size() + " surfaces");
Log.d(TAG, "configureOutputs with " + outputsStr);
}
- stopPreview();
+ try {
+ stopPreview();
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Received device exception in configure call: ", e);
+ mDeviceState.setError(
+ CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
+ return;
+ }
+
/*
* Try to release the previous preview's surface texture earlier if we end up
* using a different one; this also reduces the likelihood of getting into a deadlock
@@ -333,6 +346,11 @@ public class RequestThreadManager {
mCamera.setPreviewTexture(/*surfaceTexture*/null);
} catch (IOException e) {
Log.w(TAG, "Failed to clear prior SurfaceTexture, may cause GL deadlock: ", e);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Received device exception in configure call: ", e);
+ mDeviceState.setError(
+ CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
+ return;
}
if (mGLThreadManager != null) {
@@ -346,10 +364,15 @@ public class RequestThreadManager {
mJpegSurfaceIds.clear();
mPreviewTexture = null;
+ List<Size> previewOutputSizes = new ArrayList<>();
+ List<Size> callbackOutputSizes = new ArrayList<>();
+
int facing = mCharacteristics.get(CameraCharacteristics.LENS_FACING);
int orientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
if (outputs != null) {
- for (Surface s : outputs) {
+ for (Pair<Surface, Size> outPair : outputs) {
+ Surface s = outPair.first;
+ Size outSize = outPair.second;
try {
int format = LegacyCameraDevice.detectSurfaceType(s);
LegacyCameraDevice.setSurfaceOrientation(s, facing, orientation);
@@ -362,9 +385,11 @@ public class RequestThreadManager {
}
mJpegSurfaceIds.add(LegacyCameraDevice.getSurfaceId(s));
mCallbackOutputs.add(s);
+ callbackOutputSizes.add(outSize);
break;
default:
mPreviewOutputs.add(s);
+ previewOutputSizes.add(outSize);
break;
}
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
@@ -391,18 +416,9 @@ public class RequestThreadManager {
mParams.setPreviewFpsRange(bestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
bestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
- if (mPreviewOutputs.size() > 0) {
- List<Size> outputSizes = new ArrayList<>(outputs.size());
- for (Surface s : mPreviewOutputs) {
- try {
- Size size = LegacyCameraDevice.getSurfaceSize(s);
- outputSizes.add(size);
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.w(TAG, "Surface abandoned, skipping...", e);
- }
- }
+ if (previewOutputSizes.size() > 0) {
- Size largestOutput = SizeAreaComparator.findLargestByArea(outputSizes);
+ Size largestOutput = SizeAreaComparator.findLargestByArea(previewOutputSizes);
// Find largest jpeg dimension - assume to have the same aspect ratio as sensor.
Size largestJpegDimen = ParameterUtils.getLargestSupportedJpegSizeByArea(mParams);
@@ -439,7 +455,8 @@ public class RequestThreadManager {
}
}
- Size smallestSupportedJpegSize = calculatePictureSize(mCallbackOutputs, mParams);
+ Size smallestSupportedJpegSize = calculatePictureSize(mCallbackOutputs,
+ callbackOutputSizes, mParams);
if (smallestSupportedJpegSize != null) {
/*
* Set takePicture size to the smallest supported JPEG size large enough
@@ -457,14 +474,26 @@ public class RequestThreadManager {
mGLThreadManager.start();
}
mGLThreadManager.waitUntilStarted();
- mGLThreadManager.setConfigurationAndWait(mPreviewOutputs, mCaptureCollector);
+ List<Pair<Surface, Size>> previews = new ArrayList<>();
+ Iterator<Size> previewSizeIter = previewOutputSizes.iterator();
+ for (Surface p : mPreviewOutputs) {
+ previews.add(new Pair<>(p, previewSizeIter.next()));
+ }
+ mGLThreadManager.setConfigurationAndWait(previews, mCaptureCollector);
mGLThreadManager.allowNewFrames();
mPreviewTexture = mGLThreadManager.getCurrentSurfaceTexture();
if (mPreviewTexture != null) {
mPreviewTexture.setOnFrameAvailableListener(mPreviewCallback);
}
- mCamera.setParameters(mParams);
+ try {
+ mCamera.setParameters(mParams);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Received device exception while configuring: ", e);
+ mDeviceState.setError(
+ CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
+
+ }
}
private void resetJpegSurfaceFormats(Collection<Surface> surfaces) {
@@ -499,26 +528,25 @@ public class RequestThreadManager {
* {@code null} if the {@code callbackOutputs} did not have any {@code JPEG}
* surfaces.
*/
- private Size calculatePictureSize(
- Collection<Surface> callbackOutputs, Camera.Parameters params) {
+ private Size calculatePictureSize( List<Surface> callbackOutputs,
+ List<Size> callbackSizes, Camera.Parameters params) {
/*
* Find the largest JPEG size (if any), from the configured outputs:
* - the api1 picture size should be set to the smallest legal size that's at least as large
* as the largest configured JPEG size
*/
- List<Size> configuredJpegSizes = new ArrayList<Size>();
+ if (callbackOutputs.size() != callbackSizes.size()) {
+ throw new IllegalStateException("Input collections must be same length");
+ }
+ List<Size> configuredJpegSizes = new ArrayList<>();
+ Iterator<Size> sizeIterator = callbackSizes.iterator();
for (Surface callbackSurface : callbackOutputs) {
- try {
-
+ Size jpegSize = sizeIterator.next();
if (!LegacyCameraDevice.containsSurfaceId(callbackSurface, mJpegSurfaceIds)) {
continue; // Ignore non-JPEG callback formats
}
- Size jpegSize = LegacyCameraDevice.getSurfaceSize(callbackSurface);
configuredJpegSizes.add(jpegSize);
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.w(TAG, "Surface abandoned, skipping...", e);
- }
}
if (!configuredJpegSizes.isEmpty()) {
/*
@@ -788,7 +816,7 @@ public class RequestThreadManager {
}
} catch (IOException e) {
- Log.e(TAG, "Received device exception: ", e);
+ Log.e(TAG, "Received device exception during capture call: ", e);
mDeviceState.setError(
CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
break;
@@ -797,6 +825,11 @@ public class RequestThreadManager {
mDeviceState.setError(
CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
break;
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Received device exception during capture call: ", e);
+ mDeviceState.setError(
+ CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
+ break;
}
if (paramsChanged) {
@@ -873,9 +906,11 @@ public class RequestThreadManager {
}
if (mGLThreadManager != null) {
mGLThreadManager.quit();
+ mGLThreadManager = null;
}
if (mCamera != null) {
mCamera.release();
+ mCamera = null;
}
resetJpegSurfaceFormats(mCallbackOutputs);
break;
@@ -937,14 +972,16 @@ public class RequestThreadManager {
* Quit the request thread, and clean up everything.
*/
public void quit() {
- Handler handler = mRequestThread.waitAndGetHandler();
- handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
- mRequestThread.quitSafely();
- try {
- mRequestThread.join();
- } catch (InterruptedException e) {
- Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
- mRequestThread.getName(), mRequestThread.getId()));
+ if (!mQuit.getAndSet(true)) { // Avoid sending messages on dead thread's handler.
+ Handler handler = mRequestThread.waitAndGetHandler();
+ handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
+ mRequestThread.quitSafely();
+ try {
+ mRequestThread.join();
+ } catch (InterruptedException e) {
+ Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
+ mRequestThread.getName(), mRequestThread.getId()));
+ }
}
}
@@ -994,7 +1031,7 @@ public class RequestThreadManager {
*
* @param outputs a {@link java.util.Collection} of outputs to configure.
*/
- public void configure(Collection<Surface> outputs) {
+ public void configure(Collection<Pair<Surface, Size>> outputs) {
Handler handler = mRequestThread.waitAndGetHandler();
final ConditionVariable condition = new ConditionVariable(/*closed*/false);
ConfigureHolder holder = new ConfigureHolder(condition, outputs);
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index c0d1d5e..615b2c8 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -58,6 +58,11 @@ public class SurfaceTextureRenderer {
private static final int GLES_VERSION = 2;
private static final int PBUFFER_PIXEL_BYTES = 4;
+ private static final int FLIP_TYPE_NONE = 0;
+ private static final int FLIP_TYPE_HORIZONTAL = 1;
+ private static final int FLIP_TYPE_VERTICAL = 2;
+ private static final int FLIP_TYPE_BOTH = FLIP_TYPE_HORIZONTAL | FLIP_TYPE_VERTICAL;
+
private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
private EGLConfig mConfigs;
@@ -82,8 +87,8 @@ public class SurfaceTextureRenderer {
private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
- // Sampling is mirrored across the vertical axis to undo horizontal flip from the front camera
- private static final float[] sFrontCameraTriangleVertices = {
+ // Sampling is mirrored across the horizontal axis
+ private static final float[] sHorizontalFlipTriangleVertices = {
// X, Y, Z, U, V
-1.0f, -1.0f, 0, 1.f, 0.f,
1.0f, -1.0f, 0, 0.f, 0.f,
@@ -91,8 +96,26 @@ public class SurfaceTextureRenderer {
1.0f, 1.0f, 0, 0.f, 1.f,
};
+ // Sampling is mirrored across the vertical axis
+ private static final float[] sVerticalFlipTriangleVertices = {
+ // X, Y, Z, U, V
+ -1.0f, -1.0f, 0, 0.f, 1.f,
+ 1.0f, -1.0f, 0, 1.f, 1.f,
+ -1.0f, 1.0f, 0, 0.f, 0.f,
+ 1.0f, 1.0f, 0, 1.f, 0.f,
+ };
+
+ // Sampling is mirrored across the both axes
+ private static final float[] sBothFlipTriangleVertices = {
+ // X, Y, Z, U, V
+ -1.0f, -1.0f, 0, 1.f, 1.f,
+ 1.0f, -1.0f, 0, 0.f, 1.f,
+ -1.0f, 1.0f, 0, 1.f, 0.f,
+ 1.0f, 1.0f, 0, 0.f, 0.f,
+ };
+
// Sampling is 1:1 for a straight copy for the back camera
- private static final float[] sBackCameraTriangleVertices = {
+ private static final float[] sRegularTriangleVertices = {
// X, Y, Z, U, V
-1.0f, -1.0f, 0, 0.f, 0.f,
1.0f, -1.0f, 0, 1.f, 0.f,
@@ -100,7 +123,11 @@ public class SurfaceTextureRenderer {
1.0f, 1.0f, 0, 1.f, 1.f,
};
- private FloatBuffer mTriangleVertices;
+ private FloatBuffer mRegularTriangleVertices;
+ private FloatBuffer mHorizontalFlipTriangleVertices;
+ private FloatBuffer mVerticalFlipTriangleVertices;
+ private FloatBuffer mBothFlipTriangleVertices;
+ private final int mFacing;
/**
* As used in this file, this vertex shader maps a unit square to the view, and
@@ -148,15 +175,27 @@ public class SurfaceTextureRenderer {
private static final String LEGACY_PERF_PROPERTY = "persist.camera.legacy_perf";
public SurfaceTextureRenderer(int facing) {
- if (facing == CameraCharacteristics.LENS_FACING_BACK) {
- mTriangleVertices = ByteBuffer.allocateDirect(sBackCameraTriangleVertices.length *
- FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
- mTriangleVertices.put(sBackCameraTriangleVertices).position(0);
- } else {
- mTriangleVertices = ByteBuffer.allocateDirect(sFrontCameraTriangleVertices.length *
- FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
- mTriangleVertices.put(sFrontCameraTriangleVertices).position(0);
- }
+ mFacing = facing;
+
+ mRegularTriangleVertices = ByteBuffer.allocateDirect(sRegularTriangleVertices.length *
+ FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
+ mRegularTriangleVertices.put(sRegularTriangleVertices).position(0);
+
+ mHorizontalFlipTriangleVertices = ByteBuffer.allocateDirect(
+ sHorizontalFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
+ order(ByteOrder.nativeOrder()).asFloatBuffer();
+ mHorizontalFlipTriangleVertices.put(sHorizontalFlipTriangleVertices).position(0);
+
+ mVerticalFlipTriangleVertices = ByteBuffer.allocateDirect(
+ sVerticalFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
+ order(ByteOrder.nativeOrder()).asFloatBuffer();
+ mVerticalFlipTriangleVertices.put(sVerticalFlipTriangleVertices).position(0);
+
+ mBothFlipTriangleVertices = ByteBuffer.allocateDirect(
+ sBothFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
+ order(ByteOrder.nativeOrder()).asFloatBuffer();
+ mBothFlipTriangleVertices.put(sBothFlipTriangleVertices).position(0);
+
Matrix.setIdentityM(mSTMatrix, 0);
}
@@ -209,7 +248,7 @@ public class SurfaceTextureRenderer {
return program;
}
- private void drawFrame(SurfaceTexture st, int width, int height) {
+ private void drawFrame(SurfaceTexture st, int width, int height, int flipType) {
checkGlError("onDrawFrame start");
st.getTransformMatrix(mSTMatrix);
@@ -230,21 +269,23 @@ public class SurfaceTextureRenderer {
throw new IllegalStateException("Illegal intermediate texture with dimension of 0");
}
- // Letterbox or pillerbox output dimensions into intermediate dimensions.
+ // Letterbox or pillar-box output dimensions into intermediate dimensions.
RectF intermediate = new RectF(/*left*/0, /*top*/0, /*right*/texWidth, /*bottom*/texHeight);
RectF output = new RectF(/*left*/0, /*top*/0, /*right*/width, /*bottom*/height);
android.graphics.Matrix boxingXform = new android.graphics.Matrix();
boxingXform.setRectToRect(output, intermediate, android.graphics.Matrix.ScaleToFit.CENTER);
boxingXform.mapRect(output);
- // Find scaling factor from pillerboxed/letterboxed output dimensions to intermediate
+ // Find scaling factor from pillar-boxed/letter-boxed output dimensions to intermediate
// buffer dimensions.
float scaleX = intermediate.width() / output.width();
float scaleY = intermediate.height() / output.height();
- // Scale opposite dimension in clip coordinates so output is letterboxed/pillerboxed into
- // the intermediate dimensions (rather than vice-versa).
- Matrix.scaleM(mMVPMatrix, /*offset*/0, /*x*/scaleY, /*y*/scaleX, /*z*/1);
+ // Intermediate texture is implicitly scaled to 'fill' the output dimensions in clip space
+ // coordinates in the shader. To avoid stretching, we need to scale the larger dimension
+ // of the intermediate buffer so that the output buffer is actually letter-boxed
+ // or pillar-boxed into the intermediate buffer after clipping.
+ Matrix.scaleM(mMVPMatrix, /*offset*/0, /*x*/scaleX, /*y*/scaleY, /*z*/1);
if (DEBUG) {
Log.d(TAG, "Scaling factors (S_x = " + scaleX + ",S_y = " + scaleY + ") used for " +
@@ -266,16 +307,32 @@ public class SurfaceTextureRenderer {
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
- mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+ FloatBuffer triangleVertices;
+ switch(flipType) {
+ case FLIP_TYPE_HORIZONTAL:
+ triangleVertices = mHorizontalFlipTriangleVertices;
+ break;
+ case FLIP_TYPE_VERTICAL:
+ triangleVertices = mVerticalFlipTriangleVertices;
+ break;
+ case FLIP_TYPE_BOTH:
+ triangleVertices = mBothFlipTriangleVertices;
+ break;
+ default:
+ triangleVertices = mRegularTriangleVertices;
+ break;
+ }
+
+ triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(maPositionHandle, VERTEX_POS_SIZE, GLES20.GL_FLOAT,
- /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+ /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
checkGlError("glVertexAttribPointer maPosition");
GLES20.glEnableVertexAttribArray(maPositionHandle);
checkGlError("glEnableVertexAttribArray maPositionHandle");
- mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+ triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
GLES20.glVertexAttribPointer(maTextureHandle, VERTEX_UV_SIZE, GLES20.GL_FLOAT,
- /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+ /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
checkGlError("glVertexAttribPointer maTextureHandle");
GLES20.glEnableVertexAttribArray(maTextureHandle);
checkGlError("glEnableVertexAttribArray maTextureHandle");
@@ -397,16 +454,9 @@ public class SurfaceTextureRenderer {
EGL14.EGL_NONE
};
for (EGLSurfaceHolder holder : surfaces) {
- try {
- Size size = LegacyCameraDevice.getSurfaceSize(holder.surface);
- holder.width = size.getWidth();
- holder.height = size.getHeight();
- holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs,
- holder.surface, surfaceAttribs, /*offset*/ 0);
- checkEglError("eglCreateWindowSurface");
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.w(TAG, "Surface abandoned, skipping...", e);
- }
+ holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs,
+ holder.surface, surfaceAttribs, /*offset*/ 0);
+ checkEglError("eglCreateWindowSurface");
}
}
@@ -417,24 +467,17 @@ public class SurfaceTextureRenderer {
int maxLength = 0;
for (EGLSurfaceHolder holder : surfaces) {
- try {
- Size size = LegacyCameraDevice.getSurfaceSize(holder.surface);
- int length = size.getWidth() * size.getHeight();
- // Find max surface size, ensure PBuffer can hold this many pixels
- maxLength = (length > maxLength) ? length : maxLength;
- int[] surfaceAttribs = {
- EGL14.EGL_WIDTH, size.getWidth(),
- EGL14.EGL_HEIGHT, size.getHeight(),
- EGL14.EGL_NONE
- };
- holder.width = size.getWidth();
- holder.height = size.getHeight();
- holder.eglSurface =
- EGL14.eglCreatePbufferSurface(mEGLDisplay, mConfigs, surfaceAttribs, 0);
- checkEglError("eglCreatePbufferSurface");
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.w(TAG, "Surface abandoned, skipping...", e);
- }
+ int length = holder.width * holder.height;
+ // Find max surface size, ensure PBuffer can hold this many pixels
+ maxLength = (length > maxLength) ? length : maxLength;
+ int[] surfaceAttribs = {
+ EGL14.EGL_WIDTH, holder.width,
+ EGL14.EGL_HEIGHT, holder.height,
+ EGL14.EGL_NONE
+ };
+ holder.eglSurface =
+ EGL14.eglCreatePbufferSurface(mEGLDisplay, mConfigs, surfaceAttribs, 0);
+ checkEglError("eglCreatePbufferSurface");
}
mPBufferPixels = ByteBuffer.allocateDirect(maxLength * PBUFFER_PIXEL_BYTES)
.order(ByteOrder.nativeOrder());
@@ -569,7 +612,7 @@ public class SurfaceTextureRenderer {
*
* @param surfaces a {@link Collection} of surfaces.
*/
- public void configureSurfaces(Collection<Surface> surfaces) {
+ public void configureSurfaces(Collection<Pair<Surface, Size>> surfaces) {
releaseEGLContext();
if (surfaces == null || surfaces.size() == 0) {
@@ -577,18 +620,20 @@ public class SurfaceTextureRenderer {
return;
}
- for (Surface s : surfaces) {
+ for (Pair<Surface, Size> p : surfaces) {
+ Surface s = p.first;
+ Size surfaceSize = p.second;
// If pixel conversions aren't handled by egl, use a pbuffer
try {
+ EGLSurfaceHolder holder = new EGLSurfaceHolder();
+ holder.surface = s;
+ holder.width = surfaceSize.getWidth();
+ holder.height = surfaceSize.getHeight();
if (LegacyCameraDevice.needsConversion(s)) {
// Always override to YV12 output for YUV surface formats.
LegacyCameraDevice.setSurfaceFormat(s, ImageFormat.YV12);
- EGLSurfaceHolder holder = new EGLSurfaceHolder();
- holder.surface = s;
mConversionSurfaces.add(holder);
} else {
- EGLSurfaceHolder holder = new EGLSurfaceHolder();
- holder.surface = s;
mSurfaces.add(holder);
}
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
@@ -672,12 +717,15 @@ public class SurfaceTextureRenderer {
List<Long> targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces);
for (EGLSurfaceHolder holder : mSurfaces) {
if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
- makeCurrent(holder.eglSurface);
- try {
+ try{
LegacyCameraDevice.setSurfaceDimens(holder.surface, holder.width,
holder.height);
+ makeCurrent(holder.eglSurface);
+
LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
- drawFrame(mSurfaceTexture, holder.width, holder.height);
+ drawFrame(mSurfaceTexture, holder.width, holder.height,
+ (mFacing == CameraCharacteristics.LENS_FACING_FRONT) ?
+ FLIP_TYPE_HORIZONTAL : FLIP_TYPE_NONE);
swapBuffers(holder.eglSurface);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, dropping frame. ", e);
@@ -687,7 +735,10 @@ public class SurfaceTextureRenderer {
for (EGLSurfaceHolder holder : mConversionSurfaces) {
if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
makeCurrent(holder.eglSurface);
- drawFrame(mSurfaceTexture, holder.width, holder.height);
+ // glReadPixels reads from the bottom of the buffer, so add an extra vertical flip
+ drawFrame(mSurfaceTexture, holder.width, holder.height,
+ (mFacing == CameraCharacteristics.LENS_FACING_FRONT) ?
+ FLIP_TYPE_BOTH : FLIP_TYPE_VERTICAL);
mPBufferPixels.clear();
GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
@@ -695,10 +746,11 @@ public class SurfaceTextureRenderer {
try {
int format = LegacyCameraDevice.detectSurfaceType(holder.surface);
+ LegacyCameraDevice.setSurfaceDimens(holder.surface, holder.width,
+ holder.height);
LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
LegacyCameraDevice.produceFrame(holder.surface, mPBufferPixels.array(),
holder.width, holder.height, format);
- swapBuffers(holder.eglSurface);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, dropping frame. ", e);
}
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 5d226e3..479c842 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -22,6 +22,9 @@ import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.utils.HashCodeHelpers;
+import android.hardware.camera2.legacy.LegacyCameraDevice;
+import android.hardware.camera2.legacy.LegacyMetadataMapper;
+import android.hardware.camera2.legacy.LegacyExceptionUtils.BufferQueueAbandonedException;
import android.view.Surface;
import android.util.Log;
import android.util.Range;
@@ -292,13 +295,21 @@ public final class StreamConfigurationMap {
* </li>
* </ul>
*
- * This is not an exhaustive list; see the particular class's documentation for further
+ * <p>Surfaces from flexible sources will return true even if the exact size of the Surface does
+ * not match a camera-supported size, as long as the format (or class) is supported and the
+ * camera device supports a size that is equal to or less than 1080p in that format. If such as
+ * Surface is used to create a capture session, it will have its size rounded to the nearest
+ * supported size, below or equal to 1080p. Flexible sources include SurfaceView, SurfaceTexture,
+ * and ImageReader.</p>
+ *
+ * <p>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}
+ * @throws IllegalArgumentException if the Surface endpoint is no longer valid
*
* @see CameraDevice#createCaptureSession
* @see #isOutputSupportedFor(Class)
@@ -306,9 +317,37 @@ public final class StreamConfigurationMap {
public boolean isOutputSupportedFor(Surface surface) {
checkNotNull(surface, "surface must not be null");
- throw new UnsupportedOperationException("Not implemented yet");
+ Size surfaceSize;
+ int surfaceFormat = -1;
+ try {
+ surfaceSize = LegacyCameraDevice.getSurfaceSize(surface);
+ surfaceFormat = LegacyCameraDevice.detectSurfaceType(surface);
+ } catch(BufferQueueAbandonedException e) {
+ throw new IllegalArgumentException("Abandoned surface", e);
+ }
+
+ // See if consumer is flexible.
+ boolean isFlexible = LegacyCameraDevice.isFlexibleConsumer(surface);
- // TODO: JNI function that checks the Surface's IGraphicBufferProducer state
+ // Override RGB formats to IMPLEMENTATION_DEFINED, b/9487482
+ if ((surfaceFormat >= LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888 &&
+ surfaceFormat <= LegacyMetadataMapper.HAL_PIXEL_FORMAT_BGRA_8888)) {
+ surfaceFormat = LegacyMetadataMapper.HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
+ }
+
+ for (StreamConfiguration config : mConfigurations) {
+ if (config.getFormat() == surfaceFormat && config.isOutput()) {
+ // Mathing format, either need exact size match, or a flexible consumer
+ // and a size no bigger than MAX_DIMEN_FOR_ROUNDING
+ if (config.getSize().equals(surfaceSize)) {
+ return true;
+ } else if (isFlexible &&
+ (config.getSize().getWidth() <= LegacyCameraDevice.MAX_DIMEN_FOR_ROUNDING)) {
+ return true;
+ }
+ }
+ }
+ return false;
}
/**
@@ -1027,7 +1066,8 @@ public final class StreamConfigurationMap {
int i = 0;
for (int format : getFormatsMap(output).keySet()) {
- if (format != HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
+ if (format != HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED &&
+ format != HAL_PIXEL_FORMAT_RAW_OPAQUE) {
formats[i++] = format;
}
}
@@ -1089,6 +1129,10 @@ public final class StreamConfigurationMap {
if (formatsMap.containsKey(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED)) {
size -= 1;
}
+ if (formatsMap.containsKey(HAL_PIXEL_FORMAT_RAW_OPAQUE)) {
+ size -= 1;
+ }
+
return size;
}
diff --git a/core/java/android/hardware/camera2/utils/ArrayUtils.java b/core/java/android/hardware/camera2/utils/ArrayUtils.java
index 5a78bbd..79a335c 100644
--- a/core/java/android/hardware/camera2/utils/ArrayUtils.java
+++ b/core/java/android/hardware/camera2/utils/ArrayUtils.java
@@ -117,7 +117,7 @@ public class ArrayUtils {
// Guard against unexpected values
if (strIndex < 0) {
- Log.w(TAG, "Ignoring invalid value " + str);
+ if (VERBOSE) Log.v(TAG, "Ignoring invalid value " + str);
continue;
}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 8447dde..bb162153 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -172,9 +172,12 @@ public abstract class DisplayManagerInternal {
// If true, enables automatic brightness control.
public boolean useAutoBrightness;
- //If true, scales the brightness to half of desired.
+ // If true, scales the brightness to half of desired.
public boolean lowPowerMode;
+ // If true, applies a brightness boost.
+ public boolean boostScreenBrightness;
+
// 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
@@ -216,6 +219,7 @@ public abstract class DisplayManagerInternal {
useAutoBrightness = other.useAutoBrightness;
blockScreenOn = other.blockScreenOn;
lowPowerMode = other.lowPowerMode;
+ boostScreenBrightness = other.boostScreenBrightness;
dozeScreenBrightness = other.dozeScreenBrightness;
dozeScreenState = other.dozeScreenState;
}
@@ -235,6 +239,7 @@ public abstract class DisplayManagerInternal {
&& useAutoBrightness == other.useAutoBrightness
&& blockScreenOn == other.blockScreenOn
&& lowPowerMode == other.lowPowerMode
+ && boostScreenBrightness == other.boostScreenBrightness
&& dozeScreenBrightness == other.dozeScreenBrightness
&& dozeScreenState == other.dozeScreenState;
}
@@ -253,6 +258,7 @@ public abstract class DisplayManagerInternal {
+ ", useAutoBrightness=" + useAutoBrightness
+ ", blockScreenOn=" + blockScreenOn
+ ", lowPowerMode=" + lowPowerMode
+ + ", boostScreenBrightness=" + boostScreenBrightness
+ ", dozeScreenBrightness=" + dozeScreenBrightness
+ ", dozeScreenState=" + Display.stateToString(dozeScreenState);
}
diff --git a/core/java/android/hardware/hdmi/HdmiClient.java b/core/java/android/hardware/hdmi/HdmiClient.java
index aba90e4..45a79e1 100644
--- a/core/java/android/hardware/hdmi/HdmiClient.java
+++ b/core/java/android/hardware/hdmi/HdmiClient.java
@@ -3,13 +3,12 @@ package android.hardware.hdmi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.hardware.hdmi.HdmiControlManager.VendorCommandListener;
-import android.hardware.hdmi.IHdmiVendorCommandListener;
import android.os.RemoteException;
import android.util.Log;
/**
* Parent for classes of various HDMI-CEC device type used to access
- * {@link HdmiControlService}. Contains methods and data used in common.
+ * the HDMI control system service. Contains methods and data used in common.
*
* @hide
*/
@@ -17,11 +16,13 @@ import android.util.Log;
public abstract class HdmiClient {
private static final String TAG = "HdmiClient";
- protected final IHdmiControlService mService;
+ /* package */ final IHdmiControlService mService;
- protected abstract int getDeviceType();
+ private IHdmiVendorCommandListener mIHdmiVendorCommandListener;
- public HdmiClient(IHdmiControlService service) {
+ /* package */ abstract int getDeviceType();
+
+ /* package */ HdmiClient(IHdmiControlService service) {
mService = service;
}
@@ -41,7 +42,7 @@ public abstract class HdmiClient {
}
/**
- * Send a key event to other logical device.
+ * Sends a key event to other logical device.
*
* @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
* @param isPressed true if this is key press event
@@ -55,7 +56,7 @@ public abstract class HdmiClient {
}
/**
- * Send vendor-specific command.
+ * Sends vendor-specific command.
*
* @param targetAddress address of the target device
* @param params vendor-specific parameter. For &lt;Vendor Command With ID&gt; do not
@@ -72,18 +73,23 @@ public abstract class HdmiClient {
}
/**
- * Add a listener used to receive incoming vendor-specific command.
+ * Sets a listener used to receive incoming vendor-specific command.
*
* @param listener listener object
*/
- public void addVendorCommandListener(@NonNull VendorCommandListener listener) {
+ public void setVendorCommandListener(@NonNull VendorCommandListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener cannot be null");
}
+ if (mIHdmiVendorCommandListener != null) {
+ throw new IllegalStateException("listener was already set");
+ }
try {
- mService.addVendorCommandListener(getListenerWrapper(listener), getDeviceType());
+ IHdmiVendorCommandListener wrappedListener = getListenerWrapper(listener);
+ mService.addVendorCommandListener(wrappedListener, getDeviceType());
+ mIHdmiVendorCommandListener = wrappedListener;
} catch (RemoteException e) {
- Log.e(TAG, "failed to add vendor command listener: ", e);
+ Log.e(TAG, "failed to set vendor command listener: ", e);
}
}
@@ -91,8 +97,13 @@ public abstract class HdmiClient {
final VendorCommandListener listener) {
return new IHdmiVendorCommandListener.Stub() {
@Override
- public void onReceived(int srcAddress, byte[] params, boolean hasVendorId) {
- listener.onReceived(srcAddress, params, hasVendorId);
+ public void onReceived(int srcAddress, int destAddress, byte[] params,
+ boolean hasVendorId) {
+ listener.onReceived(srcAddress, destAddress, params, hasVendorId);
+ }
+ @Override
+ public void onControlStateChanged(boolean enabled, int reason) {
+ listener.onControlStateChanged(enabled, reason);
}
};
}
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 30f3576..308a219 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -21,6 +21,8 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
/**
* The {@link HdmiControlManager} class is used to send HDMI control messages
@@ -36,6 +38,8 @@ import android.os.RemoteException;
*/
@SystemApi
public final class HdmiControlManager {
+ private static final String TAG = "HdmiControlManager";
+
@Nullable private final IHdmiControlService mService;
/**
@@ -56,7 +60,7 @@ public final class HdmiControlManager {
/**
* Message used by TV to receive volume status from Audio Receiver. It should check volume value
- * that is retrieved from extra value with the key {@link #EXTRA_MESSAGE_EXTRAM_PARAM1}. If the
+ * that is retrieved from extra value with the key {@link #EXTRA_MESSAGE_EXTRA_PARAM1}. If the
* value is in range of [0,100], it is current volume of Audio Receiver. And there is another
* value, {@link #AVR_VOLUME_MUTED}, which is used to inform volume mute.
*/
@@ -71,7 +75,7 @@ public final class HdmiControlManager {
* Used as an extra field in the intent {@link #ACTION_OSD_MESSAGE}. Contains the extra value
* of the message.
*/
- public static final String EXTRA_MESSAGE_EXTRAM_PARAM1 =
+ public static final String EXTRA_MESSAGE_EXTRA_PARAM1 =
"android.hardware.hdmi.extra.MESSAGE_EXTRA_PARAM1";
/**
@@ -236,16 +240,24 @@ public final class HdmiControlManager {
/** Clear timer error - CEC is disabled. */
public static final int CLEAR_TIMER_STATUS_CEC_DISABLE = 0xA2;
+ /** The HdmiControlService is started. */
+ public static final int CONTROL_STATE_CHANGED_REASON_START = 0;
+ /** The state of HdmiControlService is changed by changing of settings. */
+ public static final int CONTROL_STATE_CHANGED_REASON_SETTING = 1;
+ /** The HdmiControlService is enabled to wake up. */
+ public static final int CONTROL_STATE_CHANGED_REASON_WAKEUP = 2;
+ /** The HdmiControlService will be disabled to standby. */
+ public static final int CONTROL_STATE_CHANGED_REASON_STANDBY = 3;
+
// 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.
+ * {@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;
@@ -331,6 +343,9 @@ public final class HdmiControlManager {
void onReceived(HdmiHotplugEvent event);
}
+ private final ArrayMap<HotplugEventListener, IHdmiHotplugEventListener>
+ mHotplugEventListeners = new ArrayMap<>();
+
/**
* Listener used to get vendor-specific commands.
*/
@@ -339,11 +354,29 @@ public final class HdmiControlManager {
* Called when a vendor command is received.
*
* @param srcAddress source logical address
+ * @param destAddress destination logical address
* @param params vendor-specific parameters
* @param hasVendorId {@code true} if the command is &lt;Vendor Command
* With ID&gt;. The first 3 bytes of params is vendor id.
*/
- void onReceived(int srcAddress, byte[] params, boolean hasVendorId);
+ void onReceived(int srcAddress, int destAddress, byte[] params, boolean hasVendorId);
+
+ /**
+ * The callback is called:
+ * <ul>
+ * <li> before HdmiControlService is disabled.
+ * <li> after HdmiControlService is enabled and the local address is assigned.
+ * </ul>
+ * The client shouldn't hold the thread too long since this is a blocking call.
+ *
+ * @param enabled {@code true} if HdmiControlService is enabled.
+ * @param reason the reason code why the state of HdmiControlService is changed.
+ * @see #CONTROL_STATE_CHANGED_REASON_START
+ * @see #CONTROL_STATE_CHANGED_REASON_SETTING
+ * @see #CONTROL_STATE_CHANGED_REASON_WAKEUP
+ * @see #CONTROL_STATE_CHANGED_REASON_STANDBY
+ */
+ void onControlStateChanged(boolean enabled, int reason);
}
/**
@@ -357,12 +390,19 @@ public final class HdmiControlManager {
*/
public void addHotplugEventListener(HotplugEventListener listener) {
if (mService == null) {
+ Log.e(TAG, "HdmiControlService is not available");
+ return;
+ }
+ if (mHotplugEventListeners.containsKey(listener)) {
+ Log.e(TAG, "listener is already registered");
return;
}
+ IHdmiHotplugEventListener wrappedListener = getHotplugEventListenerWrapper(listener);
+ mHotplugEventListeners.put(listener, wrappedListener);
try {
- mService.addHotplugEventListener(getHotplugEventListenerWrapper(listener));
+ mService.addHotplugEventListener(wrappedListener);
} catch (RemoteException e) {
- // Do nothing.
+ Log.e(TAG, "failed to add hotplug event listener: ", e);
}
}
@@ -373,12 +413,18 @@ public final class HdmiControlManager {
*/
public void removeHotplugEventListener(HotplugEventListener listener) {
if (mService == null) {
+ Log.e(TAG, "HdmiControlService is not available");
+ return;
+ }
+ IHdmiHotplugEventListener wrappedListener = mHotplugEventListeners.remove(listener);
+ if (wrappedListener == null) {
+ Log.e(TAG, "tried to remove not-registered listener");
return;
}
try {
- mService.removeHotplugEventListener(getHotplugEventListenerWrapper(listener));
+ mService.removeHotplugEventListener(wrappedListener);
} catch (RemoteException e) {
- // Do nothing.
+ Log.e(TAG, "failed to remove hotplug event listener: ", e);
}
}
diff --git a/core/java/android/hardware/hdmi/HdmiDeviceInfo.java b/core/java/android/hardware/hdmi/HdmiDeviceInfo.java
index 7abea36..48ea9a6 100644
--- a/core/java/android/hardware/hdmi/HdmiDeviceInfo.java
+++ b/core/java/android/hardware/hdmi/HdmiDeviceInfo.java
@@ -77,10 +77,19 @@ public class HdmiDeviceInfo implements Parcelable {
/** Invalid port ID */
public static final int PORT_INVALID = -1;
+ /** Invalid device ID */
+ public static final int ID_INVALID = 0xFFFF;
+
+ /** Device info used to indicate an inactivated device. */
+ public static final HdmiDeviceInfo INACTIVE_DEVICE = new HdmiDeviceInfo();
+
private static final int HDMI_DEVICE_TYPE_CEC = 0;
private static final int HDMI_DEVICE_TYPE_MHL = 1;
private static final int HDMI_DEVICE_TYPE_HARDWARE = 2;
+ // Type used to indicate the device that has relinquished its active source status.
+ private static final int HDMI_DEVICE_TYPE_INACTIVE = 100;
+
// Offset used for id value. MHL devices, for instance, will be assigned the value from
// ID_OFFSET_MHL.
private static final int ID_OFFSET_CEC = 0x0;
@@ -130,6 +139,8 @@ public class HdmiDeviceInfo implements Parcelable {
return new HdmiDeviceInfo(physicalAddress, portId, adopterId, deviceId);
case HDMI_DEVICE_TYPE_HARDWARE:
return new HdmiDeviceInfo(physicalAddress, portId);
+ case HDMI_DEVICE_TYPE_INACTIVE:
+ return HdmiDeviceInfo.INACTIVE_DEVICE;
default:
return null;
}
@@ -208,7 +219,6 @@ public class HdmiDeviceInfo implements Parcelable {
mDeviceId = -1;
mAdopterId = -1;
-
}
/**
@@ -237,14 +247,36 @@ public class HdmiDeviceInfo implements Parcelable {
}
/**
- * Return the id of the device.
+ * Constructor. Used to initialize the instance representing an inactivated device.
+ * Can be passed input change listener to indicate the active source yielded
+ * its status, hence the listener should take an appropriate action such as
+ * switching to other input.
+ */
+ public HdmiDeviceInfo() {
+ mHdmiDeviceType = HDMI_DEVICE_TYPE_INACTIVE;
+ mPhysicalAddress = PATH_INVALID;
+ mId = ID_INVALID;
+
+ mLogicalAddress = -1;
+ mDeviceType = DEVICE_INACTIVE;
+ mPortId = PORT_INVALID;
+ mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
+ mDisplayName = "Inactive";
+ mVendorId = 0;
+
+ mDeviceId = -1;
+ mAdopterId = -1;
+ }
+
+ /**
+ * Returns the id of the device.
*/
public int getId() {
return mId;
}
/**
- * Return the id to be used for CEC device.
+ * Returns the id to be used for CEC device.
*
* @param address logical address of CEC device
* @return id for CEC device
@@ -255,7 +287,7 @@ public class HdmiDeviceInfo implements Parcelable {
}
/**
- * Return the id to be used for MHL device.
+ * Returns the id to be used for MHL device.
*
* @param portId port which the MHL device is connected to
* @return id for MHL device
@@ -266,7 +298,7 @@ public class HdmiDeviceInfo implements Parcelable {
}
/**
- * Return the id to be used for hardware port.
+ * Returns the id to be used for hardware port.
*
* @param portId port id
* @return id for hardware port
@@ -276,28 +308,28 @@ public class HdmiDeviceInfo implements Parcelable {
}
/**
- * Return the CEC logical address of the device.
+ * Returns the CEC logical address of the device.
*/
public int getLogicalAddress() {
return mLogicalAddress;
}
/**
- * Return the physical address of the device.
+ * Returns the physical address of the device.
*/
public int getPhysicalAddress() {
return mPhysicalAddress;
}
/**
- * Return the port ID.
+ * Returns the port ID.
*/
public int getPortId() {
return mPortId;
}
/**
- * Return CEC type of the device. For more details, refer constants between {@link #DEVICE_TV}
+ * Returns CEC type of the device. For more details, refer constants between {@link #DEVICE_TV}
* and {@link #DEVICE_INACTIVE}.
*/
public int getDeviceType() {
@@ -305,7 +337,7 @@ public class HdmiDeviceInfo implements Parcelable {
}
/**
- * Return device's power status. It should be one of the following values.
+ * Returns device's power status. It should be one of the following values.
* <ul>
* <li>{@link HdmiControlManager#POWER_STATUS_ON}
* <li>{@link HdmiControlManager#POWER_STATUS_STANDBY}
@@ -319,30 +351,36 @@ public class HdmiDeviceInfo implements Parcelable {
}
/**
- * Return MHL device id. Return -1 for non-MHL device.
+ * Returns MHL device id. Return -1 for non-MHL device.
*/
public int getDeviceId() {
return mDeviceId;
}
/**
- * Return MHL adopter id. Return -1 for non-MHL device.
+ * Returns MHL adopter id. Return -1 for non-MHL device.
*/
public int getAdopterId() {
return mAdopterId;
}
/**
- * Return {@code true} if the device is of a type that can be an input source.
+ * Returns {@code true} if the device is of a type that can be an input source.
*/
public boolean isSourceType() {
- return mDeviceType == DEVICE_PLAYBACK
- || mDeviceType == DEVICE_RECORDER
- || mDeviceType == DEVICE_TUNER;
+ if (isCecDevice()) {
+ return mDeviceType == DEVICE_PLAYBACK
+ || mDeviceType == DEVICE_RECORDER
+ || mDeviceType == DEVICE_TUNER;
+ } else if (isMhlDevice()) {
+ return true;
+ } else {
+ return false;
+ }
}
/**
- * Return {@code true} if the device represents an HDMI-CEC device. {@code false} if the device
+ * Returns {@code true} if the device represents an HDMI-CEC device. {@code false} if the device
* is either MHL or other device.
*/
public boolean isCecDevice() {
@@ -350,7 +388,7 @@ public class HdmiDeviceInfo implements Parcelable {
}
/**
- * Return {@code true} if the device represents an MHL device. {@code false} if the device is
+ * Returns {@code true} if the device represents an MHL device. {@code false} if the device is
* either CEC or other device.
*/
public boolean isMhlDevice() {
@@ -358,14 +396,22 @@ public class HdmiDeviceInfo implements Parcelable {
}
/**
- * Return display (OSD) name of the device.
+ * Return {@code true} if the device represents an inactivated device that relinquishes
+ * its status as active source by &lt;Active Source&gt; (HDMI-CEC) or Content-off (MHL).
+ */
+ public boolean isInactivated() {
+ return mHdmiDeviceType == HDMI_DEVICE_TYPE_INACTIVE;
+ }
+
+ /**
+ * Returns display (OSD) name of the device.
*/
public String getDisplayName() {
return mDisplayName;
}
/**
- * Return vendor id of the device. Vendor id is used to distinguish devices built by other
+ * Returns vendor id of the device. Vendor id is used to distinguish devices built by other
* manufactures. This is required for vendor-specific command on CEC standard.
*/
public int getVendorId() {
@@ -373,7 +419,7 @@ public class HdmiDeviceInfo implements Parcelable {
}
/**
- * Describe the kinds of special objects contained in this Parcelable's marshalled
+ * Describes the kinds of special objects contained in this Parcelable's marshalled
* representation.
*/
@Override
@@ -382,7 +428,7 @@ public class HdmiDeviceInfo implements Parcelable {
}
/**
- * Serialize this object into a {@link Parcel}.
+ * Serializes this object into a {@link Parcel}.
*
* @param dest The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written. May be 0 or
@@ -405,6 +451,8 @@ public class HdmiDeviceInfo implements Parcelable {
dest.writeInt(mDeviceId);
dest.writeInt(mAdopterId);
break;
+ case HDMI_DEVICE_TYPE_INACTIVE:
+ // flow through
default:
// no-op
}
@@ -432,6 +480,9 @@ public class HdmiDeviceInfo implements Parcelable {
case HDMI_DEVICE_TYPE_HARDWARE:
s.append("Hardware: ");
break;
+ case HDMI_DEVICE_TYPE_INACTIVE:
+ s.append("Inactivated: ");
+ break;
default:
return "";
}
diff --git a/core/java/android/hardware/hdmi/HdmiHotplugEvent.java b/core/java/android/hardware/hdmi/HdmiHotplugEvent.java
index 7be4bc5..9476742 100644
--- a/core/java/android/hardware/hdmi/HdmiHotplugEvent.java
+++ b/core/java/android/hardware/hdmi/HdmiHotplugEvent.java
@@ -44,7 +44,7 @@ public final class HdmiHotplugEvent implements Parcelable {
}
/**
- * Return the port number for which the event occurred.
+ * Returns the port number for which the event occurred.
*
* @return port number
*/
@@ -53,7 +53,7 @@ public final class HdmiHotplugEvent implements Parcelable {
}
/**
- * Return the connection status associated with this event
+ * Returns the connection status associated with this event
*
* @return true if the device gets connected; otherwise false
*/
@@ -62,7 +62,7 @@ public final class HdmiHotplugEvent implements Parcelable {
}
/**
- * Describe the kinds of special objects contained in this Parcelable's
+ * Describes the kinds of special objects contained in this Parcelable's
* marshalled representation.
*/
@Override
@@ -71,7 +71,7 @@ public final class HdmiHotplugEvent implements Parcelable {
}
/**
- * Flatten this object in to a Parcel.
+ * Flattens 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.
@@ -86,17 +86,19 @@ public final class HdmiHotplugEvent implements Parcelable {
public static final Parcelable.Creator<HdmiHotplugEvent> CREATOR
= new Parcelable.Creator<HdmiHotplugEvent>() {
/**
- * Rebuild a {@link HdmiHotplugEvent} previously stored with
+ * Rebuilds 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
*/
+ @Override
public HdmiHotplugEvent createFromParcel(Parcel p) {
int port = p.readInt();
boolean connected = p.readByte() == 1;
return new HdmiHotplugEvent(port, connected);
}
+ @Override
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
index 85ccb74..263d6b1 100644
--- a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
+++ b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
@@ -64,12 +64,12 @@ public final class HdmiPlaybackClient extends HdmiClient {
public void onComplete(int status);
}
- HdmiPlaybackClient(IHdmiControlService service) {
+ /* package */ HdmiPlaybackClient(IHdmiControlService service) {
super(service);
}
/**
- * Perform the feature 'one touch play' from playback device to turn on display
+ * Performs the feature 'one touch play' from playback device to turn on display
* and switch the input.
*
* @param callback {@link OneTouchPlayCallback} object to get informed
@@ -90,7 +90,7 @@ public final class HdmiPlaybackClient extends HdmiClient {
}
/**
- * Get the status of display device connected through HDMI bus.
+ * Gets the status of display device connected through HDMI bus.
*
* @param callback {@link DisplayStatusCallback} object to get informed
* of the result
diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.java b/core/java/android/hardware/hdmi/HdmiPortInfo.java
index 2ec6126..1f0f45a 100644
--- a/core/java/android/hardware/hdmi/HdmiPortInfo.java
+++ b/core/java/android/hardware/hdmi/HdmiPortInfo.java
@@ -114,7 +114,7 @@ public final class HdmiPortInfo implements Parcelable {
}
/**
- * Describe the kinds of special objects contained in this Parcelable's
+ * Describes the kinds of special objects contained in this Parcelable's
* marshalled representation.
*/
@Override
@@ -136,7 +136,7 @@ public final class HdmiPortInfo implements Parcelable {
boolean cec = (source.readInt() == 1);
boolean arc = (source.readInt() == 1);
boolean mhl = (source.readInt() == 1);
- return new HdmiPortInfo(id, type, address, cec, arc, mhl);
+ return new HdmiPortInfo(id, type, address, cec, mhl, arc);
}
@Override
@@ -146,7 +146,7 @@ public final class HdmiPortInfo implements Parcelable {
};
/**
- * Serialize this object into a {@link Parcel}.
+ * Serializes this object into a {@link Parcel}.
*
* @param dest The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written.
@@ -172,4 +172,15 @@ public final class HdmiPortInfo implements Parcelable {
s.append("mhl: ").append(mMhlSupported);
return s.toString();
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof HdmiPortInfo)) {
+ return false;
+ }
+ final HdmiPortInfo other = (HdmiPortInfo) o;
+ return mId == other.mId && mType == other.mType && mAddress == other.mAddress
+ && mCecSupported == other.mCecSupported && mArcSupported == other.mArcSupported
+ && mMhlSupported == other.mMhlSupported;
+ }
}
diff --git a/core/java/android/hardware/hdmi/HdmiRecordListener.java b/core/java/android/hardware/hdmi/HdmiRecordListener.java
index f6a348a..90b7768 100644
--- a/core/java/android/hardware/hdmi/HdmiRecordListener.java
+++ b/core/java/android/hardware/hdmi/HdmiRecordListener.java
@@ -25,7 +25,7 @@ import android.hardware.hdmi.HdmiRecordSources.RecordSource;
*/
@SystemApi
public abstract class HdmiRecordListener {
- protected HdmiRecordListener() {}
+ public HdmiRecordListener() {}
/**
* Called when TV received one touch record request from record device. The client of this
@@ -34,11 +34,13 @@ public abstract class HdmiRecordListener {
* @param recorderAddress
* @return record source to be used for recording. Null if no device is available.
*/
- public abstract RecordSource getOneTouchRecordSource(int recorderAddress);
+ public abstract RecordSource onOneTouchRecordSourceRequested(int recorderAddress);
/**
* Called when one touch record is started or failed during initialization.
*
+ * @param recorderAddress An address of recorder that reports result of one touch record
+ * request
* @param result result code. For more details, please look at all constants starting with
* "ONE_TOUCH_RECORD_". Only
* {@link HdmiControlManager#ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE},
@@ -47,15 +49,17 @@ public abstract class HdmiRecordListener {
* {@link HdmiControlManager#ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT} mean normal
* start of recording; otherwise, describes failure.
*/
- public void onOneTouchRecordResult(int result) {
+ public void onOneTouchRecordResult(int recorderAddress, int result) {
}
/**
* Called when timer recording is started or failed during initialization.
*
+ * @param recorderAddress An address of recorder that reports result of timer recording
+ * request
* @param data timer status data. For more details, look at {@link TimerStatusData}.
*/
- public void onTimerRecordingResult(TimerStatusData data) {
+ public void onTimerRecordingResult(int recorderAddress, TimerStatusData data) {
}
/**
@@ -230,6 +234,8 @@ public abstract class HdmiRecordListener {
/**
* Called when receiving result for clear timer recording request.
*
+ * @param recorderAddress An address of recorder that reports result of clear timer recording
+ * request
* @param result result of clear timer. It should be one of
* {@link HdmiControlManager#CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_RECORDING}
* {@link HdmiControlManager#CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_MATCHING},
@@ -239,6 +245,6 @@ public abstract class HdmiRecordListener {
* {@link HdmiControlManager#CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE},
* {@link HdmiControlManager#CLEAR_TIMER_STATUS_CEC_DISABLE}.
*/
- public void onClearTimerRecordingResult(int result) {
+ public void onClearTimerRecordingResult(int recorderAddress, int result) {
}
}
diff --git a/core/java/android/hardware/hdmi/HdmiRecordSources.java b/core/java/android/hardware/hdmi/HdmiRecordSources.java
index dcc41fa..7e94b89 100644
--- a/core/java/android/hardware/hdmi/HdmiRecordSources.java
+++ b/core/java/android/hardware/hdmi/HdmiRecordSources.java
@@ -55,23 +55,25 @@ public final class HdmiRecordSources {
/**
* Base class for each record source.
+ * @hide
*/
- static abstract class RecordSource {
- protected final int mSourceType;
- protected final int mExtraDataSize;
+ @SystemApi
+ public static abstract class RecordSource {
+ /* package */ final int mSourceType;
+ /* package */ final int mExtraDataSize;
- protected RecordSource(int sourceType, int extraDataSize) {
+ /* package */ RecordSource(int sourceType, int extraDataSize) {
mSourceType = sourceType;
mExtraDataSize = extraDataSize;
}
- abstract int extraParamToByteArray(byte[] data, int index);
+ /* package */ abstract int extraParamToByteArray(byte[] data, int index);
- final int getDataSize(boolean includeType) {
+ /* package */ final int getDataSize(boolean includeType) {
return includeType ? mExtraDataSize + 1 : mExtraDataSize;
}
- final int toByteArray(boolean includeType, byte[] data, int index) {
+ /* package */ final int toByteArray(boolean includeType, byte[] data, int index) {
if (includeType) {
// 1 to 8 bytes (depends on source).
// {[Record Source Type]} |
@@ -92,7 +94,7 @@ public final class HdmiRecordSources {
// ---- Own source -----------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------
/**
- * Create {@link OwnSource} of own source.
+ * Creates {@link OwnSource} of own source.
*/
public static OwnSource ofOwnSource() {
return new OwnSource();
@@ -309,7 +311,7 @@ public final class HdmiRecordSources {
*/
public static final class DigitalChannelData implements DigitalServiceIdentification {
/** Identifies the logical or virtual channel number of a service. */
- private ChannelIdentifier mChannelIdentifier;
+ private final ChannelIdentifier mChannelIdentifier;
public static DigitalChannelData ofTwoNumbers(int majorNumber, int minorNumber) {
return new DigitalChannelData(
@@ -336,7 +338,7 @@ public final class HdmiRecordSources {
}
/**
- * Create {@link DigitalServiceSource} with channel type.
+ * Creates {@link DigitalServiceSource} with channel type.
*
* @param broadcastSystem digital broadcast system. It should be one of
* <ul>
@@ -387,7 +389,7 @@ public final class HdmiRecordSources {
}
/**
- * Create {@link DigitalServiceSource} of ARIB type.
+ * Creates {@link DigitalServiceSource} of ARIB type.
*
* @param aribType ARIB type. It should be one of
* <ul>
@@ -418,7 +420,7 @@ public final class HdmiRecordSources {
}
/**
- * Create {@link DigitalServiceSource} of ATSC type.
+ * Creates {@link DigitalServiceSource} of ATSC type.
*
* @param atscType ATSC type. It should be one of
* <ul>
@@ -449,7 +451,7 @@ public final class HdmiRecordSources {
}
/**
- * Create {@link DigitalServiceSource} of ATSC type.
+ * Creates {@link DigitalServiceSource} of ATSC type.
*
* @param dvbType DVB type. It should be one of
* <ul>
@@ -570,7 +572,7 @@ public final class HdmiRecordSources {
public static final int BROADCAST_SYSTEM_PAL_OTHER_SYSTEM = 31;
/**
- * Create {@link AnalogueServiceSource} of analogue service.
+ * Creates {@link AnalogueServiceSource} of analogue service.
*
* @param broadcastType
* @param frequency
@@ -613,7 +615,7 @@ public final class HdmiRecordSources {
*/
@SystemApi
public static final class AnalogueServiceSource extends RecordSource {
- static final int EXTRA_DATA_SIZE = 4;
+ /* package */ static final int EXTRA_DATA_SIZE = 4;
/** Indicates the Analogue broadcast type. */
private final int mBroadcastType;
@@ -633,7 +635,7 @@ public final class HdmiRecordSources {
}
@Override
- protected int extraParamToByteArray(byte[] data, int index) {
+ /* package */ int extraParamToByteArray(byte[] data, int index) {
// [Analogue Broadcast Type] - 1 byte
data[index] = (byte) mBroadcastType;
// [Analogue Frequency] - 2 bytes
@@ -649,7 +651,7 @@ public final class HdmiRecordSources {
// ---- External plug data ---------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------
/**
- * Create {@link ExternalPlugData} of external plug type.
+ * Creates {@link ExternalPlugData} of external plug type.
*
* @param plugNumber plug number. It should be in range of [1, 255]
* @hide
@@ -693,7 +695,7 @@ public final class HdmiRecordSources {
// ---- External physical address --------------------------------------------------------------
// ---------------------------------------------------------------------------------------------
/**
- * Create {@link ExternalPhysicalAddress} of external physical address.
+ * Creates {@link ExternalPhysicalAddress} of external physical address.
*
* @param physicalAddress
* @hide
@@ -752,11 +754,13 @@ public final class HdmiRecordSources {
}
/**
- * Check the byte array of record source.
+ * Checks the byte array of record source.
* @hide
*/
@SystemApi
public static boolean checkRecordSource(byte[] recordSource) {
+ if (recordSource == null || recordSource.length == 0) return false;
+
int recordSourceType = recordSource[0];
int extraDataSize = recordSource.length - 1;
switch (recordSourceType) {
diff --git a/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java b/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java
index 1780707..6fe13ca 100644
--- a/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java
+++ b/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java
@@ -67,7 +67,7 @@ public class HdmiTimerRecordSources {
private HdmiTimerRecordSources() {}
/**
- * Create {@link TimerRecordSource} for digital source which is used for &lt;Set Digital
+ * Creates {@link TimerRecordSource} for digital source which is used for &lt;Set Digital
* Timer&gt;.
*
* @param timerInfo timer info used for timer recording
@@ -82,7 +82,7 @@ public class HdmiTimerRecordSources {
}
/**
- * Create {@link TimerRecordSource} for analogue source which is used for &lt;Set Analogue
+ * Creates {@link TimerRecordSource} for analogue source which is used for &lt;Set Analogue
* Timer&gt;.
*
* @param timerInfo timer info used for timer recording
@@ -97,7 +97,7 @@ public class HdmiTimerRecordSources {
}
/**
- * Create {@link TimerRecordSource} for external plug which is used for &lt;Set External
+ * Creates {@link TimerRecordSource} for external plug which is used for &lt;Set External
* Timer&gt;.
*
* @param timerInfo timer info used for timer recording
@@ -112,7 +112,7 @@ public class HdmiTimerRecordSources {
}
/**
- * Create {@link TimerRecordSource} for external physical address which is used for &lt;Set
+ * Creates {@link TimerRecordSource} for external physical address which is used for &lt;Set
* External Timer&gt;.
*
* @param timerInfo timer info used for timer recording
@@ -140,7 +140,7 @@ public class HdmiTimerRecordSources {
}
/**
- * Create {@link Duration} for time value.
+ * Creates {@link Duration} for time value.
*
* @param hour hour in range of [0, 23]
* @param minute minute in range of [0, 60]
@@ -162,7 +162,7 @@ public class HdmiTimerRecordSources {
}
/**
- * Create {@link Duration} for duration value.
+ * Creates {@link Duration} for duration value.
*
* @param hour hour in range of [0, 99]
* @param minute minute in range of [0, 59]
@@ -183,22 +183,27 @@ public class HdmiTimerRecordSources {
}
}
- private static class TimeUnit {
- protected final int mHour;
- protected final int mMinute;
+ /**
+ * Base class for time-related information.
+ * @hide
+ */
+ @SystemApi
+ /* package */ static class TimeUnit {
+ /* package */ final int mHour;
+ /* package */ final int mMinute;
- protected TimeUnit(int hour, int minute) {
+ /* package */ TimeUnit(int hour, int minute) {
mHour = hour;
mMinute = minute;
}
- protected int toByteArray(byte[] data, int index) {
+ /* package */ int toByteArray(byte[] data, int index) {
data[index] = toBcdByte(mHour);
data[index + 1] = toBcdByte(mMinute);
return 2;
}
- protected static byte toBcdByte(int value) {
+ /* package */ static byte toBcdByte(int value) {
int digitOfTen = (value / 10) % 10;
int digitOfOne = value % 10;
return (byte) ((digitOfTen << 4) | digitOfOne);
@@ -247,7 +252,7 @@ public class HdmiTimerRecordSources {
RECORDING_SEQUENCE_REPEAT_SATUREDAY);
/**
- * Create {@link TimerInfo} with the given information.
+ * Creates {@link TimerInfo} with the given information.
*
* @param dayOfMonth day of month
* @param monthOfYear month of year
@@ -426,7 +431,7 @@ public class HdmiTimerRecordSources {
}
/**
- * Check the byte array of timer record source.
+ * Checks the byte array of timer record source.
* @param sourcetype
* @param recordSource
* @hide
diff --git a/core/java/android/hardware/hdmi/HdmiTvClient.java b/core/java/android/hardware/hdmi/HdmiTvClient.java
index 9d92fd9..cef17dd 100644
--- a/core/java/android/hardware/hdmi/HdmiTvClient.java
+++ b/core/java/android/hardware/hdmi/HdmiTvClient.java
@@ -24,6 +24,9 @@ import android.util.Log;
import libcore.util.EmptyArray;
+import java.util.Collections;
+import java.util.List;
+
/**
* 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
@@ -40,13 +43,13 @@ public final class HdmiTvClient extends HdmiClient {
*/
public static final int VENDOR_DATA_SIZE = 16;
- HdmiTvClient(IHdmiControlService service) {
+ /* package */ HdmiTvClient(IHdmiControlService service) {
super(service);
}
// Factory method for HdmiTvClient.
// Declared package-private. Accessed by HdmiControlManager only.
- static HdmiTvClient create(IHdmiControlService service) {
+ /* package */ static HdmiTvClient create(IHdmiControlService service) {
return new HdmiTvClient(service);
}
@@ -68,7 +71,7 @@ public final class HdmiTvClient extends HdmiClient {
}
/**
- * Select a CEC logical device to be a new active source.
+ * Selects a CEC logical device to be a new active source.
*
* @param logicalAddress logical address of the device to select
* @param callback callback to get the result with
@@ -95,7 +98,7 @@ public final class HdmiTvClient extends HdmiClient {
}
/**
- * Select a HDMI port to be a new route path.
+ * Selects a HDMI port to be a new route path.
*
* @param portId HDMI port to select
* @param callback callback to get the result with
@@ -125,7 +128,7 @@ public final class HdmiTvClient extends HdmiClient {
}
/**
- * Set the listener used to get informed of the input change event.
+ * Sets the listener used to get informed of the input change event.
*
* @param listener listener object
*/
@@ -150,7 +153,22 @@ public final class HdmiTvClient extends HdmiClient {
}
/**
- * Set system audio volume
+ * Returns all the CEC devices connected to TV.
+ *
+ * @return list of {@link HdmiDeviceInfo} for connected CEC devices.
+ * Empty list is returned if there is none.
+ */
+ public List<HdmiDeviceInfo> getDeviceList() {
+ try {
+ return mService.getDeviceList();
+ } catch (RemoteException e) {
+ Log.e("TAG", "Failed to call getDeviceList():", e);
+ return Collections.<HdmiDeviceInfo>emptyList();
+ }
+ }
+
+ /**
+ * Sets system audio volume
*
* @param oldIndex current volume index
* @param newIndex volume index to be set
@@ -165,7 +183,7 @@ public final class HdmiTvClient extends HdmiClient {
}
/**
- * Set system audio mute status
+ * Sets system audio mute status
*
* @param mute {@code true} if muted; otherwise, {@code false}
*/
@@ -178,7 +196,7 @@ public final class HdmiTvClient extends HdmiClient {
}
/**
- * Set record listener
+ * Sets record listener
*
* @param listener
*/
@@ -198,7 +216,7 @@ public final class HdmiTvClient extends HdmiClient {
@Override
public byte[] getOneTouchRecordSource(int recorderAddress) {
HdmiRecordSources.RecordSource source =
- callback.getOneTouchRecordSource(recorderAddress);
+ callback.onOneTouchRecordSourceRequested(recorderAddress);
if (source == null) {
return EmptyArray.BYTE;
}
@@ -208,31 +226,31 @@ public final class HdmiTvClient extends HdmiClient {
}
@Override
- public void onOneTouchRecordResult(int result) {
- callback.onOneTouchRecordResult(result);
+ public void onOneTouchRecordResult(int recorderAddress, int result) {
+ callback.onOneTouchRecordResult(recorderAddress, result);
}
@Override
- public void onTimerRecordingResult(int result) {
- callback.onTimerRecordingResult(
+ public void onTimerRecordingResult(int recorderAddress, int result) {
+ callback.onTimerRecordingResult(recorderAddress,
HdmiRecordListener.TimerStatusData.parseFrom(result));
}
@Override
- public void onClearTimerRecordingResult(int result) {
- callback.onClearTimerRecordingResult(result);
+ public void onClearTimerRecordingResult(int recorderAddress, int result) {
+ callback.onClearTimerRecordingResult(recorderAddress, result);
}
};
}
/**
- * Start one touch recording with the given recorder address and recorder source.
+ * Starts one touch recording with the given recorder address and recorder source.
* <p>
* Usage
* <pre>
* HdmiTvClient tvClient = ....;
* // for own source.
- * OwnSource ownSource = ownHdmiRecordSources.ownSource();
+ * OwnSource ownSource = HdmiRecordSources.ofOwnSource();
* tvClient.startOneTouchRecord(recorderAddress, ownSource);
* </pre>
*/
@@ -251,7 +269,7 @@ public final class HdmiTvClient extends HdmiClient {
}
/**
- * Stop one touch record.
+ * Stops one touch record.
*
* @param recorderAddress recorder address where recoding will be stopped
*/
@@ -264,7 +282,7 @@ public final class HdmiTvClient extends HdmiClient {
}
/**
- * Start timer recording with the given recoder address and recorder source.
+ * Starts timer recording with the given recoder address and recorder source.
* <p>
* Usage
* <pre>
@@ -313,7 +331,7 @@ public final class HdmiTvClient extends HdmiClient {
}
/**
- * Clear timer recording with the given recorder address and recording source.
+ * Clears timer recording with the given recorder address and recording source.
* For more details, please refer {@link #startTimerRecording(int, int, TimerRecordSource)}.
*/
public void clearTimerRecording(int recorderAddress, int sourceType, TimerRecordSource source) {
@@ -339,7 +357,7 @@ public final class HdmiTvClient extends HdmiClient {
}
/**
- * Set {@link HdmiMhlVendorCommandListener} to get incoming MHL vendor command.
+ * Sets {@link HdmiMhlVendorCommandListener} to get incoming MHL vendor command.
*
* @param listener to receive incoming MHL vendor command
*/
@@ -365,7 +383,7 @@ public final class HdmiTvClient extends HdmiClient {
}
/**
- * Send MHL vendor command to the device connected to a port of the given portId.
+ * Sends MHL vendor command to the device connected to a port of the given portId.
*
* @param portId id of port to send MHL vendor command
* @param offset offset in the in given data
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 4866a9a..c1e924e 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -59,6 +59,7 @@ interface IHdmiControlService {
void setSystemAudioMute(boolean mute);
void setInputChangeListener(IHdmiInputChangeListener listener);
List<HdmiDeviceInfo> getInputDevices();
+ List<HdmiDeviceInfo> getDeviceList();
void sendVendorCommand(int deviceType, int targetAddress, in byte[] params,
boolean hasVendorId);
void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType);
diff --git a/core/java/android/hardware/hdmi/IHdmiRecordListener.aidl b/core/java/android/hardware/hdmi/IHdmiRecordListener.aidl
index 44d9065..d2deb38 100644
--- a/core/java/android/hardware/hdmi/IHdmiRecordListener.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiRecordListener.aidl
@@ -31,19 +31,25 @@ package android.hardware.hdmi;
/**
* Called when one touch record is started or failed during initialization.
*
+ * @param recorderAddress An address of recorder that reports result of one touch record
+ * request
* @param result result code for one touch record
*/
- void onOneTouchRecordResult(int result);
+ void onOneTouchRecordResult(int recorderAddress, int result);
/**
* Called when timer recording is started or failed during initialization.
-
+ *
+ * @param recorderAddress An address of recorder that reports result of timer recording
+ * request
* @param result result code for timer recording
*/
- void onTimerRecordingResult(int result);
+ void onTimerRecordingResult(int recorderAddress, int result);
/**
* Called when receiving result for clear timer recording request.
*
- * @param result result of clear timer.
+ * @param recorderAddress An address of recorder that reports result of clear timer recording
+ * request
+ * @param result result of clear timer
*/
- void onClearTimerRecordingResult(int result);
+ void onClearTimerRecordingResult(int recorderAddress, int result);
} \ No newline at end of file
diff --git a/core/java/android/hardware/hdmi/IHdmiVendorCommandListener.aidl b/core/java/android/hardware/hdmi/IHdmiVendorCommandListener.aidl
index 55cc925..c708d20 100644
--- a/core/java/android/hardware/hdmi/IHdmiVendorCommandListener.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiVendorCommandListener.aidl
@@ -22,6 +22,7 @@ package android.hardware.hdmi;
*
* @hide
*/
-oneway interface IHdmiVendorCommandListener {
- void onReceived(int logicalAddress, in byte[] operands, boolean hasVendorId);
+interface IHdmiVendorCommandListener {
+ void onReceived(int logicalAddress, int destAddress, in byte[] operands, boolean hasVendorId);
+ void onControlStateChanged(boolean enabled, int reason);
}
diff --git a/core/java/android/hardware/location/GeofenceHardwareImpl.java b/core/java/android/hardware/location/GeofenceHardwareImpl.java
index 5c7a8da..6e5d064 100644
--- a/core/java/android/hardware/location/GeofenceHardwareImpl.java
+++ b/core/java/android/hardware/location/GeofenceHardwareImpl.java
@@ -23,6 +23,7 @@ import android.location.IGpsGeofenceHardware;
import android.location.Location;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IInterface;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
@@ -30,6 +31,7 @@ import android.util.Log;
import android.util.SparseArray;
import java.util.ArrayList;
+import java.util.Iterator;
/**
* This class manages the geofences which are handled by hardware.
@@ -558,8 +560,34 @@ public final class GeofenceHardwareImpl {
try {
callback.onGeofenceRemove(geofenceId, msg.arg2);
} catch (RemoteException e) {}
+ IBinder callbackBinder = callback.asBinder();
+ boolean callbackInUse = false;
synchronized (mGeofences) {
mGeofences.remove(geofenceId);
+ // Check if the underlying binder is still useful for other geofences,
+ // if no, unlink the DeathRecipient to avoid memory leak.
+ for (int i = 0; i < mGeofences.size(); i++) {
+ if (mGeofences.valueAt(i).asBinder() == callbackBinder) {
+ callbackInUse = true;
+ break;
+ }
+ }
+ }
+
+ // Remove the reaper associated with this binder.
+ if (!callbackInUse) {
+ for (Iterator<Reaper> iterator = mReapers.iterator();
+ iterator.hasNext();) {
+ Reaper reaper = iterator.next();
+ if (reaper.mCallback != null &&
+ reaper.mCallback.asBinder() == callbackBinder) {
+ iterator.remove();
+ reaper.unlinkToDeath();
+ if (DEBUG) Log.d(TAG, String.format("Removed reaper %s " +
+ "because binder %s is no longer needed.",
+ reaper, callbackBinder));
+ }
+ }
}
}
releaseWakeLock();
@@ -803,8 +831,9 @@ public final class GeofenceHardwareImpl {
@Override
public int hashCode() {
int result = 17;
- result = 31 * result + (mCallback != null ? mCallback.hashCode() : 0);
- result = 31 * result + (mMonitorCallback != null ? mMonitorCallback.hashCode() : 0);
+ result = 31 * result + (mCallback != null ? mCallback.asBinder().hashCode() : 0);
+ result = 31 * result + (mMonitorCallback != null
+ ? mMonitorCallback.asBinder().hashCode() : 0);
result = 31 * result + mMonitoringType;
return result;
}
@@ -815,9 +844,38 @@ public final class GeofenceHardwareImpl {
if (obj == this) return true;
Reaper rhs = (Reaper) obj;
- return rhs.mCallback == mCallback && rhs.mMonitorCallback == mMonitorCallback &&
+ return binderEquals(rhs.mCallback, mCallback) &&
+ binderEquals(rhs.mMonitorCallback, mMonitorCallback) &&
rhs.mMonitoringType == mMonitoringType;
}
+
+ /**
+ * Compares the underlying Binder of the given two IInterface objects and returns true if
+ * they equals. null values are accepted.
+ */
+ private boolean binderEquals(IInterface left, IInterface right) {
+ if (left == null) {
+ return right == null;
+ } else {
+ return right == null ? false : left.asBinder() == right.asBinder();
+ }
+ }
+
+ /**
+ * Unlinks this DeathRecipient.
+ */
+ private boolean unlinkToDeath() {
+ if (mMonitorCallback != null) {
+ return mMonitorCallback.asBinder().unlinkToDeath(this, 0);
+ } else if (mCallback != null) {
+ return mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+ return true;
+ }
+
+ private boolean callbackEquals(IGeofenceHardwareCallback cb) {
+ return mCallback != null && mCallback.asBinder() == cb.asBinder();
+ }
}
int getAllowedResolutionLevel(int pid, int uid) {
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 746ead2..c85e97b 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -211,6 +211,43 @@ public class SoundTrigger {
this.type = type;
this.data = data;
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(data);
+ result = prime * result + type;
+ result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
+ result = prime * result + ((vendorUuid == null) ? 0 : vendorUuid.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (!(obj instanceof SoundModel))
+ return false;
+ SoundModel other = (SoundModel) obj;
+ if (!Arrays.equals(data, other.data))
+ return false;
+ if (type != other.type)
+ return false;
+ if (uuid == null) {
+ if (other.uuid != null)
+ return false;
+ } else if (!uuid.equals(other.uuid))
+ return false;
+ if (vendorUuid == null) {
+ if (other.vendorUuid != null)
+ return false;
+ } else if (!vendorUuid.equals(other.vendorUuid))
+ return false;
+ return true;
+ }
}
/*****************************************************************************
@@ -395,6 +432,28 @@ public class SoundTrigger {
+ ", uuid=" + uuid + ", vendorUuid=" + vendorUuid
+ ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(keyphrases);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof KeyphraseSoundModel))
+ return false;
+ KeyphraseSoundModel other = (KeyphraseSoundModel) obj;
+ if (!Arrays.equals(keyphrases, other.keyphrases))
+ return false;
+ return true;
+ }
}
/**
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index af75a0a..b777e8c 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -31,6 +31,7 @@ import android.inputmethodservice.Keyboard.Key;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.TypedValue;
@@ -980,8 +981,9 @@ public class KeyboardView extends View implements View.OnClickListener {
onInitializeAccessibilityEvent(event);
String text = null;
// This is very efficient since the properties are cached.
- final boolean speakPassword = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0;
+ final boolean speakPassword = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
+ UserHandle.USER_CURRENT_OR_SELF) != 0;
// Add text only if password announcement is enabled or if headset is
// used to avoid leaking passwords.
if (speakPassword || mAudioManager.isBluetoothA2dpOn()
diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java
deleted file mode 100644
index 58d0048..0000000
--- a/core/java/android/net/BaseNetworkStateTracker.java
+++ /dev/null
@@ -1,215 +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.net;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Messenger;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import com.android.internal.util.Preconditions;
-
-/**
- * Interface to control and observe state of a specific network, hiding
- * network-specific details from {@link ConnectivityManager}. Surfaces events
- * through the registered {@link Handler} to enable {@link ConnectivityManager}
- * to respond to state changes over time.
- *
- * @hide
- */
-public abstract class BaseNetworkStateTracker implements NetworkStateTracker {
- // TODO: better document threading expectations
- // TODO: migrate to make NetworkStateTracker abstract class
-
- public static final String PROP_TCP_BUFFER_UNKNOWN = "net.tcp.buffersize.unknown";
- public static final String PROP_TCP_BUFFER_WIFI = "net.tcp.buffersize.wifi";
-
- protected Context mContext;
- private Handler mTarget;
-
- protected NetworkInfo mNetworkInfo;
- protected LinkProperties mLinkProperties;
- protected NetworkCapabilities mNetworkCapabilities;
- protected Network mNetwork = new Network(ConnectivityManager.NETID_UNSET);
-
- private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
- private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
- private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
-
- public BaseNetworkStateTracker(int networkType) {
- mNetworkInfo = new NetworkInfo(
- networkType, -1, ConnectivityManager.getNetworkTypeName(networkType), null);
- mLinkProperties = new LinkProperties();
- mNetworkCapabilities = new NetworkCapabilities();
- }
-
- protected BaseNetworkStateTracker() {
- // By default, let the sub classes construct everything
- }
-
- @Deprecated
- protected Handler getTargetHandler() {
- return mTarget;
- }
-
- protected final void dispatchStateChanged() {
- // TODO: include snapshot of other fields when sending
- mTarget.obtainMessage(EVENT_STATE_CHANGED, getNetworkInfo()).sendToTarget();
- }
-
- protected final void dispatchConfigurationChanged() {
- // TODO: include snapshot of other fields when sending
- mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED, getNetworkInfo()).sendToTarget();
- }
-
- @Override
- public void startMonitoring(Context context, Handler target) {
- mContext = Preconditions.checkNotNull(context);
- mTarget = Preconditions.checkNotNull(target);
- startMonitoringInternal();
- }
-
- protected void startMonitoringInternal() {
-
- }
-
- @Override
- public NetworkInfo getNetworkInfo() {
- return new NetworkInfo(mNetworkInfo);
- }
-
- @Override
- public LinkProperties getLinkProperties() {
- return new LinkProperties(mLinkProperties);
- }
-
- @Override
- public NetworkCapabilities getNetworkCapabilities() {
- return new NetworkCapabilities(mNetworkCapabilities);
- }
-
- @Override
- public LinkQualityInfo getLinkQualityInfo() {
- return null;
- }
-
- @Override
- public void captivePortalCheckCompleted(boolean isCaptivePortal) {
- // not implemented
- }
-
- @Override
- public boolean setRadio(boolean turnOn) {
- // Base tracker doesn't handle radios
- return true;
- }
-
- @Override
- public boolean isAvailable() {
- return mNetworkInfo.isAvailable();
- }
-
- @Override
- public void setUserDataEnable(boolean enabled) {
- // Base tracker doesn't handle enabled flags
- }
-
- @Override
- public void setPolicyDataEnable(boolean enabled) {
- // Base tracker doesn't handle enabled flags
- }
-
- @Override
- public boolean isPrivateDnsRouteSet() {
- return mPrivateDnsRouteSet.get();
- }
-
- @Override
- public void privateDnsRouteSet(boolean enabled) {
- mPrivateDnsRouteSet.set(enabled);
- }
-
- @Override
- public boolean isDefaultRouteSet() {
- return mDefaultRouteSet.get();
- }
-
- @Override
- public void defaultRouteSet(boolean enabled) {
- mDefaultRouteSet.set(enabled);
- }
-
- @Override
- public boolean isTeardownRequested() {
- return mTeardownRequested.get();
- }
-
- @Override
- public void setTeardownRequested(boolean isRequested) {
- mTeardownRequested.set(isRequested);
- }
-
- @Override
- public void setDependencyMet(boolean met) {
- // Base tracker doesn't handle dependencies
- }
-
- @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() {
- if (mLinkProperties != null) {
- return mLinkProperties.getInterfaceName();
- } else {
- return null;
- }
- }
-
- @Override
- public void startSampling(SamplingDataTracker.SamplingSnapshot s) {
- // nothing to do
- }
-
- @Override
- 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 9194ca8..0832d81 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -37,6 +37,7 @@ import android.os.Messenger;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Log;
@@ -71,7 +72,6 @@ import libcore.net.event.NetworkEventDispatcher;
*/
public class ConnectivityManager {
private static final String TAG = "ConnectivityManager";
- private static final boolean LEGACY_DBG = true; // STOPSHIP
/**
* A change in network connectivity has occurred. A default connection has either
@@ -101,7 +101,7 @@ public class ConnectivityManager {
/**
* Identical to {@link #CONNECTIVITY_ACTION} broadcast, but sent without any
- * applicable {@link Settings.Global#CONNECTIVITY_CHANGE_DELAY}.
+ * historic {@link Settings.Global#CONNECTIVITY_CHANGE_DELAY}.
*
* @hide
*/
@@ -429,18 +429,6 @@ public class ConnectivityManager {
public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
/**
- * Default value for {@link Settings.Global#CONNECTIVITY_CHANGE_DELAY} in
- * milliseconds. This was introduced because IPv6 routes seem to take a
- * moment to settle - trying network activity before the routes are adjusted
- * can lead to packets using the wrong interface or having the wrong IP address.
- * This delay is a bit crude, but in the future hopefully we will have kernel
- * notifications letting us know when it's safe to use the new network.
- *
- * @hide
- */
- public static final int CONNECTIVITY_CHANGE_DELAY_DEFAULT = 3000;
-
- /**
* @hide
*/
public final static int REQUEST_ID_UNSET = 0;
@@ -453,6 +441,13 @@ public class ConnectivityManager {
public static final int NETID_UNSET = 0;
private final IConnectivityManager mService;
+ /**
+ * A kludge to facilitate static access where a Context pointer isn't available, like in the
+ * case of the static set/getProcessDefaultNetwork methods and from the Network class.
+ * TODO: Remove this after deprecating the static methods in favor of non-static methods or
+ * methods that take a Context argument.
+ */
+ private static ConnectivityManager sInstance;
private INetworkManagementService mNMService;
@@ -722,6 +717,19 @@ public class ConnectivityManager {
}
/**
+ * Returns an array of of {@link NetworkCapabilities} objects, representing
+ * the Networks that applications run by the given user will use by default.
+ * @hide
+ */
+ public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) {
+ try {
+ return mService.getDefaultNetworkCapabilitiesForUser(userId);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
* Returns details about the Provisioning or currently active default data network. When
* connected, this network is the default route for outgoing connections.
* You should always check {@link NetworkInfo#isConnected()} before initiating
@@ -880,14 +888,6 @@ public class ConnectivityManager {
NetworkRequest request = null;
synchronized (sLegacyRequests) {
- if (LEGACY_DBG) {
- Log.d(TAG, "Looking for legacyRequest for netCap with hash: " + netCap + " (" +
- netCap.hashCode() + ")");
- Log.d(TAG, "sLegacyRequests has:");
- for (NetworkCapabilities nc : sLegacyRequests.keySet()) {
- Log.d(TAG, " " + nc + " (" + nc.hashCode() + ")");
- }
- }
LegacyRequest l = sLegacyRequests.get(netCap);
if (l != null) {
Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest);
@@ -933,10 +933,8 @@ public class ConnectivityManager {
return -1;
}
- NetworkCallback networkCallback = removeRequestForFeature(netCap);
- if (networkCallback != null) {
+ if (removeRequestForFeature(netCap)) {
Log.d(TAG, "stopUsingNetworkFeature for " + networkType + ", " + feature);
- unregisterNetworkCallback(networkCallback);
}
return 1;
}
@@ -1012,60 +1010,57 @@ public class ConnectivityManager {
return null;
}
+ /**
+ * Guess what the network request was trying to say so that the resulting
+ * network is accessible via the legacy (deprecated) API such as
+ * requestRouteToHost.
+ * This means we should try to be fairly preceise about transport and
+ * capability but ignore things such as networkSpecifier.
+ * If the request has more than one transport or capability it doesn't
+ * match the old legacy requests (they selected only single transport/capability)
+ * so this function cannot map the request to a single legacy type and
+ * the resulting network will not be available to the legacy APIs.
+ *
+ * TODO - This should be removed when the legacy APIs are removed.
+ */
private int inferLegacyTypeForNetworkCapabilities(NetworkCapabilities netCap) {
if (netCap == null) {
return TYPE_NONE;
}
+
if (!netCap.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
return TYPE_NONE;
}
+
+ String type = null;
+ int result = TYPE_NONE;
+
if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
- if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableCBS"))) {
- return TYPE_MOBILE_CBS;
- } else {
- return TYPE_NONE;
- }
- }
- if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
- if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableIMS"))) {
- return TYPE_MOBILE_IMS;
- } else {
- return TYPE_NONE;
- }
- }
- if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
- if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableFOTA"))) {
- return TYPE_MOBILE_FOTA;
- } else {
- return TYPE_NONE;
- }
- }
- if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
- if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableDUN"))) {
- return TYPE_MOBILE_DUN;
- } else {
- return TYPE_NONE;
- }
- }
- if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
- if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableSUPL"))) {
- return TYPE_MOBILE_SUPL;
- } else {
- return TYPE_NONE;
- }
- }
- if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
- if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableMMS"))) {
- return TYPE_MOBILE_MMS;
- } else {
- return TYPE_NONE;
- }
- }
- if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
- if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableHIPRI"))) {
- return TYPE_MOBILE_HIPRI;
- } else {
- return TYPE_NONE;
+ type = "enableCBS";
+ result = TYPE_MOBILE_CBS;
+ } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+ type = "enableIMS";
+ result = TYPE_MOBILE_IMS;
+ } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
+ type = "enableFOTA";
+ result = TYPE_MOBILE_FOTA;
+ } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
+ type = "enableDUN";
+ result = TYPE_MOBILE_DUN;
+ } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
+ type = "enableSUPL";
+ result = TYPE_MOBILE_SUPL;
+ } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+ type = "enableMMS";
+ result = TYPE_MOBILE_MMS;
+ } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+ type = "enableHIPRI";
+ result = TYPE_MOBILE_HIPRI;
+ }
+ if (type != null) {
+ NetworkCapabilities testCap = networkCapabilitiesForFeature(TYPE_MOBILE, type);
+ if (testCap.equalsNetCapabilities(netCap) && testCap.equalsTransportTypes(netCap)) {
+ return result;
}
}
return TYPE_NONE;
@@ -1106,6 +1101,14 @@ public class ConnectivityManager {
int expireSequenceNumber;
Network currentNetwork;
int delay = -1;
+
+ private void clearDnsBinding() {
+ if (currentNetwork != null) {
+ currentNetwork = null;
+ setProcessDefaultNetworkForHostResolution(null);
+ }
+ }
+
NetworkCallback networkCallback = new NetworkCallback() {
@Override
public void onAvailable(Network network) {
@@ -1115,10 +1118,7 @@ public class ConnectivityManager {
}
@Override
public void onLost(Network network) {
- if (network.equals(currentNetwork)) {
- currentNetwork = null;
- setProcessDefaultNetworkForHostResolution(null);
- }
+ if (network.equals(currentNetwork)) clearDnsBinding();
Log.d(TAG, "startUsingNetworkFeature lost Network:" + network);
}
};
@@ -1147,10 +1147,7 @@ public class ConnectivityManager {
LegacyRequest l = sLegacyRequests.get(netCap);
if (l == null) return;
ourSeqNum = l.expireSequenceNumber;
- if (l.expireSequenceNumber == sequenceNum) {
- unregisterNetworkCallback(l.networkCallback);
- sLegacyRequests.remove(netCap);
- }
+ if (l.expireSequenceNumber == sequenceNum) removeRequestForFeature(netCap);
}
Log.d(TAG, "expireRequest with " + ourSeqNum + ", " + sequenceNum);
}
@@ -1181,12 +1178,15 @@ public class ConnectivityManager {
}
}
- private NetworkCallback removeRequestForFeature(NetworkCapabilities netCap) {
+ private boolean removeRequestForFeature(NetworkCapabilities netCap) {
+ final LegacyRequest l;
synchronized (sLegacyRequests) {
- LegacyRequest l = sLegacyRequests.remove(netCap);
- if (l == null) return null;
- return l.networkCallback;
+ l = sLegacyRequests.remove(netCap);
}
+ if (l == null) return false;
+ unregisterNetworkCallback(l.networkCallback);
+ l.clearDnsBinding();
+ return true;
}
/**
@@ -1294,9 +1294,15 @@ public class ConnectivityManager {
if (b != null) {
try {
ITelephony it = ITelephony.Stub.asInterface(b);
- return it.getDataEnabled();
+ int subId = SubscriptionManager.getDefaultDataSubId();
+ Log.d("ConnectivityManager", "getMobileDataEnabled()+ subId=" + subId);
+ boolean retVal = it.getDataEnabled(subId);
+ Log.d("ConnectivityManager", "getMobileDataEnabled()- subId=" + subId
+ + " retVal=" + retVal);
+ return retVal;
} catch (RemoteException e) { }
}
+ Log.d("ConnectivityManager", "getMobileDataEnabled()- remote exception retVal=false");
return false;
}
@@ -1396,6 +1402,7 @@ public class ConnectivityManager {
*/
public ConnectivityManager(IConnectivityManager service) {
mService = checkNotNull(service, "missing IConnectivityManager");
+ sInstance = this;
}
/** {@hide} */
@@ -1418,6 +1425,18 @@ public class ConnectivityManager {
}
/**
+ * @deprecated - use getSystemService. This is a kludge to support static access in certain
+ * situations where a Context pointer is unavailable.
+ * @hide
+ */
+ public static ConnectivityManager getInstance() {
+ if (sInstance == null) {
+ throw new IllegalStateException("No ConnectivityManager yet constructed");
+ }
+ return sInstance;
+ }
+
+ /**
* Get the set of tetherable, available interfaces. This list is limited by
* device configuration and current interface existence.
*
@@ -1748,41 +1767,28 @@ public class ConnectivityManager {
}
/**
- * Get the HTTP proxy settings for the current default network. Note that
- * if a global proxy is set, it will override any per-network setting.
+ * Get the current default HTTP proxy settings. If a global proxy is set it will be returned,
+ * otherwise if this process is bound to a {@link Network} using
+ * {@link #setProcessDefaultNetwork} then that {@code Network}'s proxy is returned, otherwise
+ * the default network's proxy is returned.
*
* @return the {@link ProxyInfo} for the current HTTP proxy, or {@code null} if no
* HTTP proxy is active.
- *
- * <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}
+ * @hide
*/
- public ProxyInfo getProxy() {
- try {
- return mService.getProxy();
- } catch (RemoteException e) {
+ public ProxyInfo getDefaultProxy() {
+ final Network network = getProcessDefaultNetwork();
+ if (network != null) {
+ final ProxyInfo globalProxy = getGlobalProxy();
+ if (globalProxy != null) return globalProxy;
+ final LinkProperties lp = getLinkProperties(network);
+ if (lp != null) return lp.getHttpProxy();
return null;
}
- }
-
- /**
- * Sets a secondary requirement bit for the given networkType.
- * This requirement bit is generally under the control of the carrier
- * or its agents and is not directly controlled by the user.
- *
- * @param networkType The network who's dependence has changed
- * @param met Boolean - true if network use is OK, false if not
- *
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
- * {@hide}
- */
- public void setDataDependency(int networkType, boolean met) {
try {
- mService.setDataDependency(networkType, met);
+ return mService.getDefaultProxy();
} catch (RemoteException e) {
+ return null;
}
}
@@ -1867,20 +1873,6 @@ public class ConnectivityManager {
}
/**
- * Supply the backend messenger for a network tracker
- *
- * @param networkType NetworkType to set
- * @param messenger {@link Messenger}
- * {@hide}
- */
- public void supplyMessenger(int networkType, Messenger messenger) {
- try {
- mService.supplyMessenger(networkType, messenger);
- } catch (RemoteException e) {
- }
- }
-
- /**
* Check mobile provisioning.
*
* @param suggestedTimeOutMs, timeout in milliseconds
@@ -1924,45 +1916,6 @@ public class ConnectivityManager {
}
/**
- * get the information about a specific network link
- * @hide
- */
- public LinkQualityInfo getLinkQualityInfo(int networkType) {
- try {
- LinkQualityInfo li = mService.getLinkQualityInfo(networkType);
- return li;
- } catch (RemoteException e) {
- return null;
- }
- }
-
- /**
- * get the information of currently active network link
- * @hide
- */
- public LinkQualityInfo getActiveLinkQualityInfo() {
- try {
- LinkQualityInfo li = mService.getActiveLinkQualityInfo();
- return li;
- } catch (RemoteException e) {
- return null;
- }
- }
-
- /**
- * get the information of all network links
- * @hide
- */
- public LinkQualityInfo[] getAllLinkQualityInfo() {
- try {
- LinkQualityInfo[] li = mService.getAllLinkQualityInfo();
- return li;
- } catch (RemoteException e) {
- return null;
- }
- }
-
- /**
* Set sign in error notification to visible or in visible
*
* @param visible
@@ -2381,26 +2334,27 @@ public class ConnectivityManager {
/**
* The lookup key for a {@link Network} object included with the intent after
- * succesfully finding a network for the applications request. Retrieve it with
+ * successfully finding a network for the applications request. Retrieve it with
* {@link android.content.Intent#getParcelableExtra(String)}.
- * @hide
+ * <p>
+ * Note that if you intend to invoke (@link #setProcessDefaultNetwork(Network)) or
+ * {@link Network#openConnection(java.net.URL)} then you must get a
+ * ConnectivityManager instance before doing so.
*/
- public static final String EXTRA_NETWORK_REQUEST_NETWORK = "networkRequestNetwork";
+ public static final String EXTRA_NETWORK = "android.net.extra.NETWORK";
/**
* The lookup key for a {@link NetworkRequest} object included with the intent after
- * succesfully finding a network for the applications request. Retrieve it with
+ * successfully finding a network for the applications request. Retrieve it with
* {@link android.content.Intent#getParcelableExtra(String)}.
- * @hide
*/
- public static final String EXTRA_NETWORK_REQUEST_NETWORK_REQUEST =
- "networkRequestNetworkRequest";
+ public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST";
/**
* Request a network to satisfy a set of {@link NetworkCapabilities}.
*
- * This function behavies identically to the version that takes a NetworkCallback, but instead
+ * This function behaves identically to the version that takes a NetworkCallback, but instead
* of {@link NetworkCallback} a {@link PendingIntent} is used. This means
* the request may outlive the calling application and get called back when a suitable
* network is found.
@@ -2410,8 +2364,8 @@ public class ConnectivityManager {
* &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 NetworkRequest}
- * typed extra called {@link #EXTRA_NETWORK_REQUEST_NETWORK_REQUEST} containing
+ * extra called {@link #EXTRA_NETWORK} and a {@link NetworkRequest}
+ * typed extra called {@link #EXTRA_NETWORK_REQUEST} containing
* the original requests parameters. It is important to create a new,
* {@link NetworkCallback} based request before completing the processing of the
* Intent to reserve the network or it will be released shortly after the Intent
@@ -2421,21 +2375,46 @@ public class ConnectivityManager {
* 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 #unregisterNetworkCallback}.
+ * The request may be released normally by calling
+ * {@link #releaseNetworkRequest(android.app.PendingIntent)}.
*
* @param request {@link NetworkRequest} describing this request.
* @param operation Action to perform when the network is available (corresponds
* to the {@link NetworkCallback#onAvailable} call. Typically
- * comes from {@link PendingIntent#getBroadcast}.
- * @hide
+ * comes from {@link PendingIntent#getBroadcast}. Cannot be null.
*/
public void requestNetwork(NetworkRequest request, PendingIntent operation) {
+ checkPendingIntent(operation);
try {
mService.pendingRequestForNetwork(request.networkCapabilities, operation);
} catch (RemoteException e) {}
}
/**
+ * Removes a request made via {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)}
+ * <p>
+ * This method has the same behavior as {@link #unregisterNetworkCallback} with respect to
+ * releasing network resources and disconnecting.
+ *
+ * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the
+ * PendingIntent passed to
+ * {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)} with the
+ * corresponding NetworkRequest you'd like to remove. Cannot be null.
+ */
+ public void releaseNetworkRequest(PendingIntent operation) {
+ checkPendingIntent(operation);
+ try {
+ mService.releasePendingNetworkRequest(operation);
+ } catch (RemoteException e) {}
+ }
+
+ private void checkPendingIntent(PendingIntent intent) {
+ if (intent == null) {
+ throw new IllegalArgumentException("PendingIntent cannot be null.");
+ }
+ }
+
+ /**
* Registers to receive notifications about all networks which satisfy the given
* {@link NetworkRequest}. The callbacks will continue to be called until
* either the application exits or {@link #unregisterNetworkCallback} is called
@@ -2451,7 +2430,7 @@ public class ConnectivityManager {
/**
* Unregisters callbacks about and possibly releases networks originating from
* {@link #requestNetwork} and {@link #registerNetworkCallback} calls. If the
- * given {@code NetworkCallback} had previosuly been used with {@code #requestNetwork},
+ * given {@code NetworkCallback} had previously been used with {@code #requestNetwork},
* any networks that had been connected to only to satisfy that request will be
* disconnected.
*
@@ -2491,6 +2470,9 @@ public class ConnectivityManager {
return true;
}
if (NetworkUtils.bindProcessToNetwork(netId)) {
+ // Set HTTP proxy system properties to match network.
+ // TODO: Deprecate this static method and replace it with a non-static version.
+ Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy());
// Must flush DNS cache as new network may have different DNS resolutions.
InetAddress.clearDnsCache();
// Must flush socket pool as idle sockets will be bound to previous network and may
diff --git a/core/java/android/net/EthernetManager.java b/core/java/android/net/EthernetManager.java
index d965f27..f45737a 100644
--- a/core/java/android/net/EthernetManager.java
+++ b/core/java/android/net/EthernetManager.java
@@ -18,11 +18,14 @@ package android.net;
import android.content.Context;
import android.net.IEthernetManager;
+import android.net.IEthernetServiceListener;
import android.net.IpConfiguration;
-import android.net.IpConfiguration.IpAssignment;
-import android.net.IpConfiguration.ProxySettings;
+import android.os.Handler;
+import android.os.Message;
import android.os.RemoteException;
+import java.util.ArrayList;
+
/**
* A class representing the IP configuration of the Ethernet network.
*
@@ -30,9 +33,41 @@ import android.os.RemoteException;
*/
public class EthernetManager {
private static final String TAG = "EthernetManager";
+ private static final int MSG_AVAILABILITY_CHANGED = 1000;
private final Context mContext;
private final IEthernetManager mService;
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_AVAILABILITY_CHANGED) {
+ boolean isAvailable = (msg.arg1 == 1);
+ for (Listener listener : mListeners) {
+ listener.onAvailabilityChanged(isAvailable);
+ }
+ }
+ }
+ };
+ private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
+ private final IEthernetServiceListener.Stub mServiceListener =
+ new IEthernetServiceListener.Stub() {
+ @Override
+ public void onAvailabilityChanged(boolean isAvailable) {
+ mHandler.obtainMessage(
+ MSG_AVAILABILITY_CHANGED, isAvailable ? 1 : 0, 0, null).sendToTarget();
+ }
+ };
+
+ /**
+ * A listener interface to receive notification on changes in Ethernet.
+ */
+ public interface Listener {
+ /**
+ * Called when Ethernet port's availability is changed.
+ * @param isAvailable {@code true} if one or more Ethernet port exists.
+ */
+ public void onAvailabilityChanged(boolean isAvailable);
+ }
/**
* Create a new EthernetManager instance.
@@ -50,12 +85,9 @@ public class EthernetManager {
* @return the Ethernet Configuration, contained in {@link IpConfiguration}.
*/
public IpConfiguration getConfiguration() {
- if (mService == null) {
- return new IpConfiguration();
- }
try {
return mService.getConfiguration();
- } catch (RemoteException e) {
+ } catch (NullPointerException | RemoteException e) {
return new IpConfiguration();
}
}
@@ -64,12 +96,57 @@ public class EthernetManager {
* Set Ethernet configuration.
*/
public void setConfiguration(IpConfiguration config) {
- if (mService == null) {
- return;
- }
try {
mService.setConfiguration(config);
- } catch (RemoteException e) {
+ } catch (NullPointerException | RemoteException e) {
+ }
+ }
+
+ /**
+ * Indicates whether the system currently has one or more
+ * Ethernet interfaces.
+ */
+ public boolean isAvailable() {
+ try {
+ return mService.isAvailable();
+ } catch (NullPointerException | RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Adds a listener.
+ * @param listener A {@link Listener} to add.
+ * @throws IllegalArgumentException If the listener is null.
+ */
+ public void addListener(Listener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ mListeners.add(listener);
+ if (mListeners.size() == 1) {
+ try {
+ mService.addListener(mServiceListener);
+ } catch (NullPointerException | RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * Removes a listener.
+ * @param listener A {@link Listener} to remove.
+ * @throws IllegalArgumentException If the listener is null.
+ */
+ public void removeListener(Listener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ mListeners.remove(listener);
+ if (mListeners.isEmpty()) {
+ try {
+ mService.removeListener(mServiceListener);
+ } catch (NullPointerException | RemoteException e) {
+ }
}
}
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index a983d88..d8852f8 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -17,7 +17,6 @@
package android.net;
import android.app.PendingIntent;
-import android.net.LinkQualityInfo;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -50,6 +49,7 @@ interface IConnectivityManager
NetworkInfo[] getAllNetworkInfo();
Network getNetworkForType(int networkType);
Network[] getAllNetworks();
+ NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId);
NetworkInfo getProvisioningOrActiveNetworkInfo();
@@ -68,9 +68,6 @@ interface IConnectivityManager
boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);
- /** Policy control over specific {@link NetworkStateTracker}. */
- void setPolicyDataEnable(int networkType, boolean enabled);
-
int tether(String iface);
int untether(String iface);
@@ -103,9 +100,7 @@ interface IConnectivityManager
void setGlobalProxy(in ProxyInfo p);
- ProxyInfo getProxy();
-
- void setDataDependency(int networkType, boolean met);
+ ProxyInfo getDefaultProxy();
boolean prepareVpn(String oldPackage, String newPackage);
@@ -123,8 +118,6 @@ interface IConnectivityManager
void captivePortalCheckCompleted(in NetworkInfo info, boolean isCaptivePortal);
- void supplyMessenger(int networkType, in Messenger messenger);
-
int findConnectionTypeForIface(in String iface);
int checkMobileProvisioning(int suggestedTimeOutMs);
@@ -133,12 +126,6 @@ interface IConnectivityManager
String getMobileRedirectedProvisioningUrl();
- LinkQualityInfo getLinkQualityInfo(int networkType);
-
- LinkQualityInfo getActiveLinkQualityInfo();
-
- LinkQualityInfo[] getAllLinkQualityInfo();
-
void setProvisioningNotificationVisible(boolean visible, int networkType, in String action);
void setAirplaneMode(boolean enable);
@@ -156,6 +143,8 @@ interface IConnectivityManager
NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities,
in PendingIntent operation);
+ void releasePendingNetworkRequest(in PendingIntent operation);
+
NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities,
in Messenger messenger, in IBinder binder);
@@ -168,4 +157,5 @@ interface IConnectivityManager
boolean addVpnAddress(String address, int prefixLength);
boolean removeVpnAddress(String address, int prefixLength);
+ boolean setUnderlyingNetworksForVpn(in Network[] networks);
}
diff --git a/core/java/android/net/IEthernetManager.aidl b/core/java/android/net/IEthernetManager.aidl
index 3fa08f8..7a92eb9 100644
--- a/core/java/android/net/IEthernetManager.aidl
+++ b/core/java/android/net/IEthernetManager.aidl
@@ -17,6 +17,7 @@
package android.net;
import android.net.IpConfiguration;
+import android.net.IEthernetServiceListener;
/**
* Interface that answers queries about, and allows changing
@@ -27,4 +28,7 @@ interface IEthernetManager
{
IpConfiguration getConfiguration();
void setConfiguration(in IpConfiguration config);
+ boolean isAvailable();
+ void addListener(in IEthernetServiceListener listener);
+ void removeListener(in IEthernetServiceListener listener);
}
diff --git a/core/java/android/net/IEthernetServiceListener.aidl b/core/java/android/net/IEthernetServiceListener.aidl
new file mode 100644
index 0000000..356690e
--- /dev/null
+++ b/core/java/android/net/IEthernetServiceListener.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/** @hide */
+oneway interface IEthernetServiceListener
+{
+ void onAvailabilityChanged(boolean isAvailable);
+}
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index b7b8731..2c3881c 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -40,8 +40,12 @@ interface INetworkStatsService {
/** Mark given UID as being in foreground for stats purposes. */
void setUidForeground(int uid, boolean uidForeground);
+
+ /** Force update of ifaces. */
+ void forceUpdateIfaces();
/** Force update of statistics. */
void forceUpdate();
+
/** Advise persistance threshold; may be overridden internally. */
void advisePersistThreshold(long thresholdBytes);
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
index c387055..384ab1c 100644
--- a/core/java/android/net/LinkAddress.java
+++ b/core/java/android/net/LinkAddress.java
@@ -21,12 +21,14 @@ import android.os.Parcelable;
import android.util.Pair;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.UnknownHostException;
import static android.system.OsConstants.IFA_F_DADFAILED;
import static android.system.OsConstants.IFA_F_DEPRECATED;
+import static android.system.OsConstants.IFA_F_OPTIMISTIC;
import static android.system.OsConstants.IFA_F_TENTATIVE;
import static android.system.OsConstants.RT_SCOPE_HOST;
import static android.system.OsConstants.RT_SCOPE_LINK;
@@ -93,6 +95,20 @@ public class LinkAddress implements Parcelable {
}
/**
+ * Utility function to check if |address| is a Unique Local IPv6 Unicast Address
+ * (a.k.a. "ULA"; RFC 4193).
+ *
+ * Per RFC 4193 section 8, fc00::/7 identifies these addresses.
+ */
+ private boolean isIPv6ULA() {
+ if (address != null && address instanceof Inet6Address) {
+ byte[] bytes = address.getAddress();
+ return ((bytes[0] & (byte)0xfc) == (byte)0xfc);
+ }
+ return false;
+ }
+
+ /**
* Utility function for the constructors.
*/
private void init(InetAddress address, int prefixLength, int flags, int scope) {
@@ -268,8 +284,16 @@ public class LinkAddress implements Parcelable {
* @hide
*/
public boolean isGlobalPreferred() {
+ /**
+ * Note that addresses flagged as IFA_F_OPTIMISTIC are
+ * simultaneously flagged as IFA_F_TENTATIVE (when the tentative
+ * state has cleared either DAD has succeeded or failed, and both
+ * flags are cleared regardless).
+ */
return (scope == RT_SCOPE_UNIVERSE &&
- (flags & (IFA_F_DADFAILED | IFA_F_DEPRECATED | IFA_F_TENTATIVE)) == 0L);
+ !isIPv6ULA() &&
+ (flags & (IFA_F_DADFAILED | IFA_F_DEPRECATED)) == 0L &&
+ ((flags & IFA_F_TENTATIVE) == 0L || (flags & IFA_F_OPTIMISTIC) != 0L));
}
/**
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 662c576..8b0dfc9 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -493,16 +493,16 @@ public final class LinkProperties implements Parcelable {
/**
* Removes a stacked link.
*
- * If there a stacked link with the same interfacename as link, it is
+ * If there is a stacked link with the given interface name, it is
* removed. Otherwise, nothing changes.
*
- * @param link The link to remove.
+ * @param iface The interface name of 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) {
- LinkProperties removed = mStackedLinks.remove(link.getInterfaceName());
+ public boolean removeStackedLink(String iface) {
+ if (iface != null) {
+ LinkProperties removed = mStackedLinks.remove(iface);
return removed != null;
}
return false;
@@ -675,17 +675,38 @@ public final class LinkProperties implements Parcelable {
}
/**
- * Returns true if this link is provisioned for global connectivity. For IPv6, this requires an
- * IP address, default route, and DNS server. For IPv4, this requires only an IPv4 address,
- * because WifiStateMachine accepts static configurations that only specify an address but not
- * DNS servers or a default route.
+ * Returns true if this link is provisioned for global IPv4 connectivity.
+ * This requires an IP address, default route, and DNS server.
+ *
+ * @return {@code true} if the link is provisioned, {@code false} otherwise.
+ */
+ private boolean hasIPv4() {
+ return (hasIPv4Address() &&
+ hasIPv4DefaultRoute() &&
+ hasIPv4DnsServer());
+ }
+
+ /**
+ * Returns true if this link is provisioned for global IPv6 connectivity.
+ * This requires an IP address, default route, and DNS server.
+ *
+ * @return {@code true} if the link is provisioned, {@code false} otherwise.
+ */
+ private boolean hasIPv6() {
+ return (hasGlobalIPv6Address() &&
+ hasIPv6DefaultRoute() &&
+ hasIPv6DnsServer());
+ }
+
+ /**
+ * Returns true if this link is provisioned for global connectivity,
+ * for at least one Internet Protocol family.
*
* @return {@code true} if the link is provisioned, {@code false} otherwise.
* @hide
*/
public boolean isProvisioned() {
- return (hasIPv4Address() ||
- (hasGlobalIPv6Address() && hasIPv6DefaultRoute() && hasIPv6DnsServer()));
+ return (hasIPv4() || hasIPv6());
}
/**
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
deleted file mode 100644
index d8012fe..0000000
--- a/core/java/android/net/MobileDataStateTracker.java
+++ /dev/null
@@ -1,919 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.NetworkInfo.DetailedState;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.telephony.PhoneStateListener;
-import android.telephony.SignalStrength;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.Slog;
-
-import com.android.internal.telephony.DctConstants;
-import com.android.internal.telephony.ITelephony;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.util.AsyncChannel;
-
-import java.io.CharArrayWriter;
-import java.io.PrintWriter;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Track the state of mobile data connectivity. This is done by
- * receiving broadcast intents from the Phone process whenever
- * the state of data connectivity changes.
- *
- * {@hide}
- */
-public class MobileDataStateTracker extends BaseNetworkStateTracker {
-
- private static final String TAG = "MobileDataStateTracker";
- private static final boolean DBG = false;
- private static final boolean VDBG = false;
-
- private PhoneConstants.DataState mMobileDataState;
- private ITelephony mPhoneService;
-
- private String mApnType;
- private NetworkInfo mNetworkInfo;
- private boolean mTeardownRequested = false;
- private Handler mTarget;
- private Context mContext;
- private LinkProperties mLinkProperties;
- private boolean mPrivateDnsRouteSet = false;
- private boolean mDefaultRouteSet = false;
-
- // NOTE: these are only kept for debugging output; actual values are
- // maintained in DataConnectionTracker.
- protected boolean mUserDataEnabled = true;
- protected boolean mPolicyDataEnabled = true;
-
- private Handler mHandler;
- private AsyncChannel mDataConnectionTrackerAc;
-
- private AtomicBoolean mIsCaptivePortal = new AtomicBoolean(false);
-
- private SignalStrength mSignalStrength;
-
- private SamplingDataTracker mSamplingDataTracker = new SamplingDataTracker();
-
- private static final int UNKNOWN = LinkQualityInfo.UNKNOWN_INT;
-
- /**
- * Create a new MobileDataStateTracker
- * @param netType the ConnectivityManager network type
- * @param tag the name of this network
- */
- public MobileDataStateTracker(int netType, String tag) {
- mNetworkInfo = new NetworkInfo(netType,
- TelephonyManager.getDefault().getNetworkType(), tag,
- TelephonyManager.getDefault().getNetworkTypeName());
- mApnType = networkTypeToApnType(netType);
- }
-
- /**
- * Begin monitoring data connectivity.
- *
- * @param context is the current Android context
- * @param target is the Hander to which to return the events.
- */
- public void startMonitoring(Context context, Handler target) {
- mTarget = target;
- mContext = context;
-
- mHandler = new MdstHandler(target.getLooper(), this);
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
- filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN);
- filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
-
- mContext.registerReceiver(new MobileDataStateReceiver(), filter);
- mMobileDataState = PhoneConstants.DataState.DISCONNECTED;
-
- TelephonyManager tm = (TelephonyManager)mContext.getSystemService(
- Context.TELEPHONY_SERVICE);
- tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
- }
-
- private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- @Override
- public void onSignalStrengthsChanged(SignalStrength signalStrength) {
- mSignalStrength = signalStrength;
- }
- };
-
- static class MdstHandler extends Handler {
- private MobileDataStateTracker mMdst;
-
- MdstHandler(Looper looper, MobileDataStateTracker mdst) {
- super(looper);
- mMdst = mdst;
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
- if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
- if (VDBG) {
- mMdst.log("MdstHandler connected");
- }
- mMdst.mDataConnectionTrackerAc = (AsyncChannel) msg.obj;
- } else {
- if (VDBG) {
- mMdst.log("MdstHandler %s NOT connected error=" + msg.arg1);
- }
- }
- break;
- case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
- if (VDBG) mMdst.log("Disconnected from DataStateTracker");
- mMdst.mDataConnectionTrackerAc = null;
- break;
- default: {
- if (VDBG) mMdst.log("Ignorning unknown message=" + msg);
- break;
- }
- }
- }
- }
-
- public boolean isPrivateDnsRouteSet() {
- return mPrivateDnsRouteSet;
- }
-
- public void privateDnsRouteSet(boolean enabled) {
- mPrivateDnsRouteSet = enabled;
- }
-
- public NetworkInfo getNetworkInfo() {
- return mNetworkInfo;
- }
-
- public boolean isDefaultRouteSet() {
- return mDefaultRouteSet;
- }
-
- public void defaultRouteSet(boolean enabled) {
- mDefaultRouteSet = enabled;
- }
-
- /**
- * This is not implemented.
- */
- public void releaseWakeLock() {
- }
-
- private void updateLinkProperitesAndCapatilities(Intent intent) {
- mLinkProperties = intent.getParcelableExtra(
- PhoneConstants.DATA_LINK_PROPERTIES_KEY);
- if (mLinkProperties == null) {
- loge("CONNECTED event did not supply link properties.");
- mLinkProperties = new LinkProperties();
- }
- mLinkProperties.setMtu(mContext.getResources().getInteger(
- com.android.internal.R.integer.config_mobile_mtu));
- mNetworkCapabilities = intent.getParcelableExtra(
- PhoneConstants.DATA_NETWORK_CAPABILITIES_KEY);
- if (mNetworkCapabilities == null) {
- loge("CONNECTED event did not supply network capabilities.");
- mNetworkCapabilities = new NetworkCapabilities();
- }
- }
-
- private class MobileDataStateReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(TelephonyIntents.
- ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN)) {
- String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
- String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
- if (!TextUtils.equals(mApnType, apnType)) {
- return;
- }
- if (DBG) {
- log("Broadcast received: " + intent.getAction() + " apnType=" + apnType
- + " apnName=" + apnName);
- }
-
- // Make us in the connecting state until we make a new TYPE_MOBILE_PROVISIONING
- mMobileDataState = PhoneConstants.DataState.CONNECTING;
- updateLinkProperitesAndCapatilities(intent);
- mNetworkInfo.setIsConnectedToProvisioningNetwork(true);
-
- // Change state to SUSPENDED so setDetailedState
- // sends EVENT_STATE_CHANGED to connectivityService
- setDetailedState(DetailedState.SUSPENDED, "", apnName);
- } else if (intent.getAction().equals(TelephonyIntents.
- ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
- String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
- if (VDBG) {
- log(String.format("Broadcast received: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED"
- + "mApnType=%s %s received apnType=%s", mApnType,
- TextUtils.equals(apnType, mApnType) ? "==" : "!=", apnType));
- }
- if (!TextUtils.equals(apnType, mApnType)) {
- return;
- }
- // Assume this isn't a provisioning network.
- mNetworkInfo.setIsConnectedToProvisioningNetwork(false);
- if (DBG) {
- log("Broadcast received: " + intent.getAction() + " apnType=" + apnType);
- }
-
- int oldSubtype = mNetworkInfo.getSubtype();
- int newSubType = TelephonyManager.getDefault().getNetworkType();
- String subTypeName = TelephonyManager.getDefault().getNetworkTypeName();
- mNetworkInfo.setSubtype(newSubType, subTypeName);
- if (newSubType != oldSubtype && mNetworkInfo.isConnected()) {
- Message msg = mTarget.obtainMessage(EVENT_NETWORK_SUBTYPE_CHANGED,
- oldSubtype, 0, mNetworkInfo);
- msg.sendToTarget();
- }
-
- PhoneConstants.DataState state = Enum.valueOf(PhoneConstants.DataState.class,
- intent.getStringExtra(PhoneConstants.STATE_KEY));
- String reason = intent.getStringExtra(PhoneConstants.STATE_CHANGE_REASON_KEY);
- String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
- mNetworkInfo.setRoaming(intent.getBooleanExtra(
- PhoneConstants.DATA_NETWORK_ROAMING_KEY, false));
- if (VDBG) {
- log(mApnType + " setting isAvailable to " +
- intent.getBooleanExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY,false));
- }
- mNetworkInfo.setIsAvailable(!intent.getBooleanExtra(
- PhoneConstants.NETWORK_UNAVAILABLE_KEY, false));
-
- if (DBG) {
- log("Received state=" + state + ", old=" + mMobileDataState +
- ", reason=" + (reason == null ? "(unspecified)" : reason));
- }
- if (mMobileDataState != state) {
- mMobileDataState = state;
- switch (state) {
- case DISCONNECTED:
- if(isTeardownRequested()) {
- setTeardownRequested(false);
- }
-
- setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
- // can't do this here - ConnectivityService needs it to clear stuff
- // it's ok though - just leave it to be refreshed next time
- // we connect.
- //if (DBG) log("clearing mInterfaceName for "+ mApnType +
- // " as it DISCONNECTED");
- //mInterfaceName = null;
- break;
- case CONNECTING:
- setDetailedState(DetailedState.CONNECTING, reason, apnName);
- break;
- case SUSPENDED:
- setDetailedState(DetailedState.SUSPENDED, reason, apnName);
- break;
- case CONNECTED:
- updateLinkProperitesAndCapatilities(intent);
- setDetailedState(DetailedState.CONNECTED, reason, apnName);
- break;
- }
-
- if (VDBG) {
- Slog.d(TAG, "TelephonyMgr.DataConnectionStateChanged");
- if (mNetworkInfo != null) {
- Slog.d(TAG, "NetworkInfo = " + mNetworkInfo);
- Slog.d(TAG, "subType = " + mNetworkInfo.getSubtype());
- Slog.d(TAG, "subType = " + mNetworkInfo.getSubtypeName());
- }
- if (mLinkProperties != null) {
- Slog.d(TAG, "LinkProperties = " + mLinkProperties);
- } else {
- Slog.d(TAG, "LinkProperties = " );
- }
-
- if (mNetworkCapabilities != null) {
- Slog.d(TAG, mNetworkCapabilities.toString());
- } else {
- Slog.d(TAG, "NetworkCapabilities = " );
- }
- }
-
-
- /* lets not sample traffic data across state changes */
- mSamplingDataTracker.resetSamplingData();
- } else {
- // There was no state change. Check if LinkProperties has been updated.
- if (TextUtils.equals(reason, PhoneConstants.REASON_LINK_PROPERTIES_CHANGED)) {
- mLinkProperties = intent.getParcelableExtra(
- PhoneConstants.DATA_LINK_PROPERTIES_KEY);
- if (mLinkProperties == null) {
- loge("No link property in LINK_PROPERTIES change event.");
- mLinkProperties = new LinkProperties();
- }
- // Just update reason field in this NetworkInfo
- mNetworkInfo.setDetailedState(mNetworkInfo.getDetailedState(), reason,
- mNetworkInfo.getExtraInfo());
- Message msg = mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED,
- mNetworkInfo);
- msg.sendToTarget();
- }
- }
- } else if (intent.getAction().
- equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
- String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
- if (!TextUtils.equals(apnType, mApnType)) {
- if (DBG) {
- log(String.format(
- "Broadcast received: ACTION_ANY_DATA_CONNECTION_FAILED ignore, " +
- "mApnType=%s != received apnType=%s", mApnType, apnType));
- }
- return;
- }
- // Assume this isn't a provisioning network.
- mNetworkInfo.setIsConnectedToProvisioningNetwork(false);
- String reason = intent.getStringExtra(PhoneConstants.FAILURE_REASON_KEY);
- String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
- if (DBG) {
- log("Broadcast received: " + intent.getAction() +
- " reason=" + reason == null ? "null" : reason);
- }
- setDetailedState(DetailedState.FAILED, reason, apnName);
- } else {
- if (DBG) log("Broadcast received: ignore " + intent.getAction());
- }
- }
- }
-
- private void getPhoneService(boolean forceRefresh) {
- if ((mPhoneService == null) || forceRefresh) {
- mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
- }
- }
-
- /**
- * Report whether data connectivity is possible.
- */
- public boolean isAvailable() {
- return mNetworkInfo.isAvailable();
- }
-
- /**
- * Return the system properties name associated with the tcp buffer sizes
- * for this network.
- */
- public String getTcpBufferSizesPropName() {
- String networkTypeStr = "unknown";
- TelephonyManager tm = new TelephonyManager(mContext);
- //TODO We have to edit the parameter for getNetworkType regarding CDMA
- switch(tm.getNetworkType()) {
- case TelephonyManager.NETWORK_TYPE_GPRS:
- networkTypeStr = "gprs";
- break;
- case TelephonyManager.NETWORK_TYPE_EDGE:
- networkTypeStr = "edge";
- break;
- case TelephonyManager.NETWORK_TYPE_UMTS:
- networkTypeStr = "umts";
- break;
- case TelephonyManager.NETWORK_TYPE_HSDPA:
- networkTypeStr = "hsdpa";
- break;
- case TelephonyManager.NETWORK_TYPE_HSUPA:
- networkTypeStr = "hsupa";
- break;
- case TelephonyManager.NETWORK_TYPE_HSPA:
- networkTypeStr = "hspa";
- break;
- case TelephonyManager.NETWORK_TYPE_HSPAP:
- networkTypeStr = "hspap";
- break;
- case TelephonyManager.NETWORK_TYPE_CDMA:
- networkTypeStr = "cdma";
- break;
- case TelephonyManager.NETWORK_TYPE_1xRTT:
- networkTypeStr = "1xrtt";
- break;
- case TelephonyManager.NETWORK_TYPE_EVDO_0:
- networkTypeStr = "evdo";
- break;
- case TelephonyManager.NETWORK_TYPE_EVDO_A:
- networkTypeStr = "evdo";
- break;
- case TelephonyManager.NETWORK_TYPE_EVDO_B:
- networkTypeStr = "evdo";
- break;
- case TelephonyManager.NETWORK_TYPE_IDEN:
- networkTypeStr = "iden";
- break;
- case TelephonyManager.NETWORK_TYPE_LTE:
- networkTypeStr = "lte";
- break;
- case TelephonyManager.NETWORK_TYPE_EHRPD:
- networkTypeStr = "ehrpd";
- break;
- default:
- loge("unknown network type: " + tm.getNetworkType());
- }
- return "net.tcp.buffersize." + networkTypeStr;
- }
-
- /**
- * Tear down mobile data connectivity, i.e., disable the ability to create
- * mobile data connections.
- * TODO - make async and return nothing?
- */
- public boolean teardown() {
- setTeardownRequested(true);
- return (setEnableApn(mApnType, false) != PhoneConstants.APN_REQUEST_FAILED);
- }
-
- /**
- * @return true if this is ready to operate
- */
- public boolean isReady() {
- return mDataConnectionTrackerAc != null;
- }
-
- @Override
- public void captivePortalCheckCompleted(boolean isCaptivePortal) {
- if (mIsCaptivePortal.getAndSet(isCaptivePortal) != isCaptivePortal) {
- // Captive portal change enable/disable failing fast
- setEnableFailFastMobileData(
- isCaptivePortal ? DctConstants.ENABLED : DctConstants.DISABLED);
- }
- }
-
- /**
- * Record the detailed state of a network, and if it is a
- * change from the previous state, send a notification to
- * any listeners.
- * @param state the new {@code DetailedState}
- * @param reason a {@code String} indicating a reason for the state change,
- * if one was supplied. May be {@code null}.
- * @param extraInfo optional {@code String} providing extra information about the state change
- */
- private void setDetailedState(NetworkInfo.DetailedState state, String reason,
- String extraInfo) {
- if (DBG) log("setDetailed state, old ="
- + mNetworkInfo.getDetailedState() + " and new state=" + state);
- if (state != mNetworkInfo.getDetailedState()) {
- boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
- String lastReason = mNetworkInfo.getReason();
- /*
- * If a reason was supplied when the CONNECTING state was entered, and no
- * reason was supplied for entering the CONNECTED state, then retain the
- * reason that was supplied when going to CONNECTING.
- */
- if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
- && lastReason != null)
- reason = lastReason;
- mNetworkInfo.setDetailedState(state, reason, extraInfo);
- Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, new NetworkInfo(mNetworkInfo));
- msg.sendToTarget();
- }
- }
-
- public void setTeardownRequested(boolean isRequested) {
- mTeardownRequested = isRequested;
- }
-
- public boolean isTeardownRequested() {
- return mTeardownRequested;
- }
-
- /**
- * Re-enable mobile data connectivity after a {@link #teardown()}.
- * TODO - make async and always get a notification?
- */
- public boolean reconnect() {
- boolean retValue = false; //connected or expect to be?
- setTeardownRequested(false);
- switch (setEnableApn(mApnType, true)) {
- case PhoneConstants.APN_ALREADY_ACTIVE:
- // need to set self to CONNECTING so the below message is handled.
- retValue = true;
- break;
- case PhoneConstants.APN_REQUEST_STARTED:
- // set IDLE here , avoid the following second FAILED not sent out
- mNetworkInfo.setDetailedState(DetailedState.IDLE, null, null);
- retValue = true;
- break;
- case PhoneConstants.APN_REQUEST_FAILED:
- case PhoneConstants.APN_TYPE_NOT_AVAILABLE:
- break;
- default:
- loge("Error in reconnect - unexpected response.");
- break;
- }
- return retValue;
- }
-
- /**
- * Turn on or off the mobile radio. No connectivity will be possible while the
- * radio is off. The operation is a no-op if the radio is already in the desired state.
- * @param turnOn {@code true} if the radio should be turned on, {@code false} if
- */
- public boolean setRadio(boolean turnOn) {
- getPhoneService(false);
- /*
- * If the phone process has crashed in the past, we'll get a
- * RemoteException and need to re-reference the service.
- */
- for (int retry = 0; retry < 2; retry++) {
- if (mPhoneService == null) {
- loge("Ignoring mobile radio request because could not acquire PhoneService");
- break;
- }
-
- try {
- return mPhoneService.setRadio(turnOn);
- } catch (RemoteException e) {
- if (retry == 0) getPhoneService(true);
- }
- }
-
- loge("Could not set radio power to " + (turnOn ? "on" : "off"));
- return false;
- }
-
-
- public void setInternalDataEnable(boolean enabled) {
- if (DBG) log("setInternalDataEnable: E enabled=" + enabled);
- final AsyncChannel channel = mDataConnectionTrackerAc;
- if (channel != null) {
- channel.sendMessage(DctConstants.EVENT_SET_INTERNAL_DATA_ENABLE,
- enabled ? DctConstants.ENABLED : DctConstants.DISABLED);
- }
- if (VDBG) log("setInternalDataEnable: X enabled=" + enabled);
- }
-
- @Override
- public void setUserDataEnable(boolean enabled) {
- if (DBG) log("setUserDataEnable: E enabled=" + enabled);
- final AsyncChannel channel = mDataConnectionTrackerAc;
- if (channel != null) {
- channel.sendMessage(DctConstants.CMD_SET_USER_DATA_ENABLE,
- enabled ? DctConstants.ENABLED : DctConstants.DISABLED);
- mUserDataEnabled = enabled;
- }
- if (VDBG) log("setUserDataEnable: X enabled=" + enabled);
- }
-
- @Override
- public void setPolicyDataEnable(boolean enabled) {
- if (DBG) log("setPolicyDataEnable(enabled=" + enabled + ")");
- final AsyncChannel channel = mDataConnectionTrackerAc;
- if (channel != null) {
- channel.sendMessage(DctConstants.CMD_SET_POLICY_DATA_ENABLE,
- enabled ? DctConstants.ENABLED : DctConstants.DISABLED);
- mPolicyDataEnabled = enabled;
- }
- }
-
- /**
- * Eanble/disable FailFast
- *
- * @param enabled is DctConstants.ENABLED/DISABLED
- */
- public void setEnableFailFastMobileData(int enabled) {
- if (DBG) log("setEnableFailFastMobileData(enabled=" + enabled + ")");
- final AsyncChannel channel = mDataConnectionTrackerAc;
- if (channel != null) {
- channel.sendMessage(DctConstants.CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA, enabled);
- }
- }
-
- /**
- * carrier dependency is met/unmet
- * @param met
- */
- public void setDependencyMet(boolean met) {
- Bundle bundle = Bundle.forPair(DctConstants.APN_TYPE_KEY, mApnType);
- try {
- if (DBG) log("setDependencyMet: E met=" + met);
- Message msg = Message.obtain();
- msg.what = DctConstants.CMD_SET_DEPENDENCY_MET;
- msg.arg1 = (met ? DctConstants.ENABLED : DctConstants.DISABLED);
- msg.setData(bundle);
- mDataConnectionTrackerAc.sendMessage(msg);
- if (VDBG) log("setDependencyMet: X met=" + met);
- } catch (NullPointerException e) {
- loge("setDependencyMet: X mAc was null" + e);
- }
- }
-
- /**
- * Inform DCT mobile provisioning has started, it ends when provisioning completes.
- */
- public void enableMobileProvisioning(String url) {
- if (DBG) log("enableMobileProvisioning(url=" + url + ")");
- final AsyncChannel channel = mDataConnectionTrackerAc;
- if (channel != null) {
- Message msg = Message.obtain();
- msg.what = DctConstants.CMD_ENABLE_MOBILE_PROVISIONING;
- msg.setData(Bundle.forPair(DctConstants.PROVISIONING_URL_KEY, url));
- channel.sendMessage(msg);
- }
- }
-
- /**
- * Return if this network is the provisioning network. Valid only if connected.
- * @param met
- */
- public boolean isProvisioningNetwork() {
- boolean retVal;
- try {
- Message msg = Message.obtain();
- msg.what = DctConstants.CMD_IS_PROVISIONING_APN;
- msg.setData(Bundle.forPair(DctConstants.APN_TYPE_KEY, mApnType));
- Message result = mDataConnectionTrackerAc.sendMessageSynchronously(msg);
- retVal = result.arg1 == DctConstants.ENABLED;
- } catch (NullPointerException e) {
- loge("isProvisioningNetwork: X " + e);
- retVal = false;
- }
- if (DBG) log("isProvisioningNetwork: retVal=" + retVal);
- return retVal;
- }
-
- @Override
- public void addStackedLink(LinkProperties link) {
- mLinkProperties.addStackedLink(link);
- }
-
- @Override
- public void removeStackedLink(LinkProperties link) {
- mLinkProperties.removeStackedLink(link);
- }
-
- @Override
- public String toString() {
- final CharArrayWriter writer = new CharArrayWriter();
- final PrintWriter pw = new PrintWriter(writer);
- pw.print("Mobile data state: "); pw.println(mMobileDataState);
- pw.print("Data enabled: user="); pw.print(mUserDataEnabled);
- pw.print(", policy="); pw.println(mPolicyDataEnabled);
- return writer.toString();
- }
-
- /**
- * Internal method supporting the ENABLE_MMS feature.
- * @param apnType the type of APN to be enabled or disabled (e.g., mms)
- * @param enable {@code true} to enable the specified APN type,
- * {@code false} to disable it.
- * @return an integer value representing the outcome of the request.
- */
- private int setEnableApn(String apnType, boolean enable) {
- getPhoneService(false);
- /*
- * If the phone process has crashed in the past, we'll get a
- * RemoteException and need to re-reference the service.
- */
- for (int retry = 0; retry < 2; retry++) {
- if (mPhoneService == null) {
- loge("Ignoring feature request because could not acquire PhoneService");
- break;
- }
-
-// try {
-// if (enable) {
-// return mPhoneService.enableApnType(apnType);
-// } else {
-// return mPhoneService.disableApnType(apnType);
-// }
-// } catch (RemoteException e) {
-// if (retry == 0) getPhoneService(true);
-// }
- }
-
- loge("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\"");
- return PhoneConstants.APN_REQUEST_FAILED;
- }
-
- public static String networkTypeToApnType(int netType) {
- switch(netType) {
- case ConnectivityManager.TYPE_MOBILE:
- return PhoneConstants.APN_TYPE_DEFAULT; // TODO - use just one of these
- case ConnectivityManager.TYPE_MOBILE_MMS:
- return PhoneConstants.APN_TYPE_MMS;
- case ConnectivityManager.TYPE_MOBILE_SUPL:
- return PhoneConstants.APN_TYPE_SUPL;
- case ConnectivityManager.TYPE_MOBILE_DUN:
- return PhoneConstants.APN_TYPE_DUN;
- case ConnectivityManager.TYPE_MOBILE_HIPRI:
- return PhoneConstants.APN_TYPE_HIPRI;
- case ConnectivityManager.TYPE_MOBILE_FOTA:
- return PhoneConstants.APN_TYPE_FOTA;
- case ConnectivityManager.TYPE_MOBILE_IMS:
- return PhoneConstants.APN_TYPE_IMS;
- case ConnectivityManager.TYPE_MOBILE_CBS:
- return PhoneConstants.APN_TYPE_CBS;
- case ConnectivityManager.TYPE_MOBILE_IA:
- return PhoneConstants.APN_TYPE_IA;
- case ConnectivityManager.TYPE_MOBILE_EMERGENCY:
- return PhoneConstants.APN_TYPE_EMERGENCY;
- default:
- sloge("Error mapping networkType " + netType + " to apnType.");
- return null;
- }
- }
-
-
- /**
- * @see android.net.NetworkStateTracker#getLinkProperties()
- */
- @Override
- public LinkProperties getLinkProperties() {
- return new LinkProperties(mLinkProperties);
- }
-
- public void supplyMessenger(Messenger messenger) {
- if (VDBG) log(mApnType + " got supplyMessenger");
- AsyncChannel ac = new AsyncChannel();
- ac.connect(mContext, MobileDataStateTracker.this.mHandler, messenger);
- }
-
- private void log(String s) {
- Slog.d(TAG, mApnType + ": " + s);
- }
-
- private void loge(String s) {
- Slog.e(TAG, mApnType + ": " + s);
- }
-
- static private void sloge(String s) {
- Slog.e(TAG, s);
- }
-
- @Override
- public LinkQualityInfo getLinkQualityInfo() {
- if (mNetworkInfo == null || mNetworkInfo.getType() == ConnectivityManager.TYPE_NONE) {
- // no data available yet; just return
- return null;
- }
-
- MobileLinkQualityInfo li = new MobileLinkQualityInfo();
-
- li.setNetworkType(mNetworkInfo.getType());
-
- mSamplingDataTracker.setCommonLinkQualityInfoFields(li);
-
- if (mNetworkInfo.getSubtype() != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
- li.setMobileNetworkType(mNetworkInfo.getSubtype());
-
- NetworkDataEntry entry = getNetworkDataEntry(mNetworkInfo.getSubtype());
- if (entry != null) {
- li.setTheoreticalRxBandwidth(entry.downloadBandwidth);
- li.setTheoreticalRxBandwidth(entry.uploadBandwidth);
- li.setTheoreticalLatency(entry.latency);
- }
-
- if (mSignalStrength != null) {
- li.setNormalizedSignalStrength(getNormalizedSignalStrength(
- li.getMobileNetworkType(), mSignalStrength));
- }
- }
-
- SignalStrength ss = mSignalStrength;
- if (ss != null) {
-
- li.setRssi(ss.getGsmSignalStrength());
- li.setGsmErrorRate(ss.getGsmBitErrorRate());
- li.setCdmaDbm(ss.getCdmaDbm());
- li.setCdmaEcio(ss.getCdmaEcio());
- li.setEvdoDbm(ss.getEvdoDbm());
- li.setEvdoEcio(ss.getEvdoEcio());
- li.setEvdoSnr(ss.getEvdoSnr());
- li.setLteSignalStrength(ss.getLteSignalStrength());
- li.setLteRsrp(ss.getLteRsrp());
- li.setLteRsrq(ss.getLteRsrq());
- li.setLteRssnr(ss.getLteRssnr());
- li.setLteCqi(ss.getLteCqi());
- }
-
- if (VDBG) {
- Slog.d(TAG, "Returning LinkQualityInfo with"
- + " MobileNetworkType = " + String.valueOf(li.getMobileNetworkType())
- + " Theoretical Rx BW = " + String.valueOf(li.getTheoreticalRxBandwidth())
- + " gsm Signal Strength = " + String.valueOf(li.getRssi())
- + " cdma Signal Strength = " + String.valueOf(li.getCdmaDbm())
- + " evdo Signal Strength = " + String.valueOf(li.getEvdoDbm())
- + " Lte Signal Strength = " + String.valueOf(li.getLteSignalStrength()));
- }
-
- return li;
- }
-
- static class NetworkDataEntry {
- public int networkType;
- public int downloadBandwidth; // in kbps
- public int uploadBandwidth; // in kbps
- public int latency; // in millisecond
-
- NetworkDataEntry(int i1, int i2, int i3, int i4) {
- networkType = i1;
- downloadBandwidth = i2;
- uploadBandwidth = i3;
- latency = i4;
- }
- }
-
- private static NetworkDataEntry [] mTheoreticalBWTable = new NetworkDataEntry[] {
- new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EDGE, 237, 118, UNKNOWN),
- new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_GPRS, 48, 40, UNKNOWN),
- new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_UMTS, 384, 64, UNKNOWN),
- new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSDPA, 14400, UNKNOWN, UNKNOWN),
- new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSUPA, 14400, 5760, UNKNOWN),
- new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSPA, 14400, 5760, UNKNOWN),
- new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSPAP, 21000, 5760, UNKNOWN),
- new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_CDMA, UNKNOWN, UNKNOWN, UNKNOWN),
- new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_1xRTT, UNKNOWN, UNKNOWN, UNKNOWN),
- new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EVDO_0, 2468, 153, UNKNOWN),
- new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EVDO_A, 3072, 1800, UNKNOWN),
- new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EVDO_B, 14700, 1800, UNKNOWN),
- new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_IDEN, UNKNOWN, UNKNOWN, UNKNOWN),
- new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_LTE, 100000, 50000, UNKNOWN),
- new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EHRPD, UNKNOWN, UNKNOWN, UNKNOWN),
- };
-
- private static NetworkDataEntry getNetworkDataEntry(int networkType) {
- for (NetworkDataEntry entry : mTheoreticalBWTable) {
- if (entry.networkType == networkType) {
- return entry;
- }
- }
-
- Slog.e(TAG, "Could not find Theoretical BW entry for " + String.valueOf(networkType));
- return null;
- }
-
- private static int getNormalizedSignalStrength(int networkType, SignalStrength ss) {
-
- int level;
-
- switch(networkType) {
- case TelephonyManager.NETWORK_TYPE_EDGE:
- case TelephonyManager.NETWORK_TYPE_GPRS:
- case TelephonyManager.NETWORK_TYPE_UMTS:
- case TelephonyManager.NETWORK_TYPE_HSDPA:
- case TelephonyManager.NETWORK_TYPE_HSUPA:
- case TelephonyManager.NETWORK_TYPE_HSPA:
- case TelephonyManager.NETWORK_TYPE_HSPAP:
- level = ss.getGsmLevel();
- break;
- case TelephonyManager.NETWORK_TYPE_CDMA:
- case TelephonyManager.NETWORK_TYPE_1xRTT:
- level = ss.getCdmaLevel();
- break;
- case TelephonyManager.NETWORK_TYPE_EVDO_0:
- case TelephonyManager.NETWORK_TYPE_EVDO_A:
- case TelephonyManager.NETWORK_TYPE_EVDO_B:
- level = ss.getEvdoLevel();
- break;
- case TelephonyManager.NETWORK_TYPE_LTE:
- level = ss.getLteLevel();
- break;
- case TelephonyManager.NETWORK_TYPE_IDEN:
- case TelephonyManager.NETWORK_TYPE_EHRPD:
- default:
- return UNKNOWN;
- }
-
- return (level * LinkQualityInfo.NORMALIZED_SIGNAL_STRENGTH_RANGE) /
- SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
- }
-
- @Override
- public void startSampling(SamplingDataTracker.SamplingSnapshot s) {
- mSamplingDataTracker.startSampling(s);
- }
-
- @Override
- public void stopSampling(SamplingDataTracker.SamplingSnapshot s) {
- mSamplingDataTracker.stopSampling(s);
- }
-}
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index ddb8e94..dfe2413 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -20,10 +20,13 @@ import android.os.Parcelable;
import android.os.Parcel;
import android.system.ErrnoException;
+import java.io.FileDescriptor;
import java.io.IOException;
+import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
+import java.net.ProxySelector;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
@@ -240,16 +243,46 @@ public class Network implements Parcelable {
* @see java.net.URL#openConnection()
*/
public URLConnection openConnection(URL url) throws IOException {
+ final ConnectivityManager cm = ConnectivityManager.getInstance();
+ // TODO: Should this be optimized to avoid fetching the global proxy for every request?
+ ProxyInfo proxyInfo = cm.getGlobalProxy();
+ if (proxyInfo == null) {
+ // TODO: Should this be optimized to avoid fetching LinkProperties for every request?
+ final LinkProperties lp = cm.getLinkProperties(this);
+ if (lp != null) proxyInfo = lp.getHttpProxy();
+ }
+ java.net.Proxy proxy = null;
+ if (proxyInfo != null) {
+ proxy = proxyInfo.makeProxy();
+ } else {
+ proxy = java.net.Proxy.NO_PROXY;
+ }
+ return openConnection(url, proxy);
+ }
+
+ /**
+ * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent
+ * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}.
+ *
+ * @param proxy the proxy through which the connection will be established.
+ * @return a {@code URLConnection} to the resource referred to by this URL.
+ * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS.
+ * @throws IllegalArgumentException if the argument proxy is null.
+ * @throws IOException if an error occurs while opening the connection.
+ * @see java.net.URL#openConnection()
+ * @hide
+ */
+ public URLConnection openConnection(URL url, java.net.Proxy proxy) throws IOException {
+ if (proxy == null) throw new IllegalArgumentException("proxy is null");
maybeInitHttpClient();
String protocol = url.getProtocol();
OkUrlFactory okUrlFactory;
// TODO: HttpHandler creates OkUrlFactory instances that share the default ResponseCache.
// Could this cause unexpected behavior?
- // TODO: Should the network's proxy be specified?
if (protocol.equals("http")) {
- okUrlFactory = HttpHandler.createHttpOkUrlFactory(null /* proxy */);
+ okUrlFactory = HttpHandler.createHttpOkUrlFactory(proxy);
} else if (protocol.equals("https")) {
- okUrlFactory = HttpsHandler.createHttpsOkUrlFactory(null /* proxy */);
+ okUrlFactory = HttpsHandler.createHttpsOkUrlFactory(proxy);
} else {
// OkHttp only supports HTTP and HTTPS and returns a null URLStreamHandler if
// passed another protocol.
@@ -265,18 +298,40 @@ public class Network implements Parcelable {
}
/**
+ * Binds the specified {@link DatagramSocket} to this {@code Network}. All data traffic on the
+ * socket will be sent on this {@code Network}, irrespective of any process-wide network binding
+ * set by {@link ConnectivityManager#setProcessDefaultNetwork}. The socket must not be
+ * connected.
+ */
+ public void bindSocket(DatagramSocket socket) throws IOException {
+ // Apparently, the kernel doesn't update a connected UDP socket's routing upon mark changes.
+ if (socket.isConnected()) {
+ throw new SocketException("Socket is connected");
+ }
+ // Query a property of the underlying socket to ensure that the socket's file descriptor
+ // exists, is available to bind to a network and is not closed.
+ socket.getReuseAddress();
+ bindSocketFd(socket.getFileDescriptor$());
+ }
+
+ /**
* Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket
* will be sent on this {@code Network}, irrespective of any process-wide network binding set by
* {@link ConnectivityManager#setProcessDefaultNetwork}. The socket must not be connected.
*/
public void bindSocket(Socket socket) throws IOException {
+ // Apparently, the kernel doesn't update a connected TCP socket's routing upon mark changes.
if (socket.isConnected()) {
throw new SocketException("Socket is connected");
}
- // Query a property of the underlying socket to ensure the underlying
- // socket exists so a file descriptor is available to bind to a network.
+ // Query a property of the underlying socket to ensure that the socket's file descriptor
+ // exists, is available to bind to a network and is not closed.
socket.getReuseAddress();
- int err = NetworkUtils.bindSocketToNetwork(socket.getFileDescriptor$().getInt$(), netId);
+ bindSocketFd(socket.getFileDescriptor$());
+ }
+
+ private void bindSocketFd(FileDescriptor fd) throws IOException {
+ int err = NetworkUtils.bindSocketToNetwork(fd.getInt$(), netId);
if (err != 0) {
// bindSocketToNetwork returns negative errno.
throw new ErrnoException("Binding socket to network " + netId, -err)
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 53f9fcd..a7f9c5b 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -19,16 +19,7 @@ package android.net;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
-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
@@ -36,15 +27,12 @@ import java.util.Set;
*
* 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),
+ * needs high bandwidth and risk obsolescence 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;
-
/**
* @hide
*/
@@ -166,9 +154,16 @@ public final class NetworkCapabilities implements Parcelable {
*/
public static final int NET_CAPABILITY_NOT_VPN = 15;
+ /**
+ * Indicates that connectivity on this network was successfully validated. For example, for a
+ * network with NET_CAPABILITY_INTERNET, it means that Internet connectivity was successfully
+ * detected.
+ * @hide
+ */
+ public static final int NET_CAPABILITY_VALIDATED = 16;
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_VPN;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_VALIDATED;
/**
* Adds the given capability to this {@code NetworkCapability} instance.
@@ -247,7 +242,8 @@ public final class NetworkCapabilities implements Parcelable {
return ((nc.mNetworkCapabilities & this.mNetworkCapabilities) == this.mNetworkCapabilities);
}
- private boolean equalsNetCapabilities(NetworkCapabilities nc) {
+ /** @hide */
+ public boolean equalsNetCapabilities(NetworkCapabilities nc) {
return (nc.mNetworkCapabilities == this.mNetworkCapabilities);
}
@@ -356,7 +352,8 @@ public final class NetworkCapabilities implements Parcelable {
return ((this.mTransportTypes == 0) ||
((this.mTransportTypes & nc.mTransportTypes) != 0));
}
- private boolean equalsTransportTypes(NetworkCapabilities nc) {
+ /** @hide */
+ public boolean equalsTransportTypes(NetworkCapabilities nc) {
return (nc.mTransportTypes == this.mTransportTypes);
}
@@ -541,9 +538,11 @@ public final class NetworkCapabilities implements Parcelable {
(TextUtils.isEmpty(mNetworkSpecifier) ? 0 : mNetworkSpecifier.hashCode() * 17));
}
+ @Override
public int describeContents() {
return 0;
}
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(mNetworkCapabilities);
dest.writeLong(mTransportTypes);
@@ -553,6 +552,7 @@ public final class NetworkCapabilities implements Parcelable {
}
public static final Creator<NetworkCapabilities> CREATOR =
new Creator<NetworkCapabilities>() {
+ @Override
public NetworkCapabilities createFromParcel(Parcel in) {
NetworkCapabilities netCap = new NetworkCapabilities();
@@ -563,11 +563,13 @@ public final class NetworkCapabilities implements Parcelable {
netCap.mNetworkSpecifier = in.readString();
return netCap;
}
+ @Override
public NetworkCapabilities[] newArray(int size) {
return new NetworkCapabilities[size];
}
};
+ @Override
public String toString() {
int[] types = getTransportTypes();
String transports = (types.length > 0 ? " Transports: " : "");
diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java
index 6ddd8b3..64d0fcf 100644
--- a/core/java/android/net/NetworkFactory.java
+++ b/core/java/android/net/NetworkFactory.java
@@ -219,16 +219,21 @@ public class NetworkFactory extends Handler {
}
private void evalRequest(NetworkRequestInfo n) {
+ if (VDBG) log("evalRequest");
if (n.requested == false && n.score < mScore &&
n.request.networkCapabilities.satisfiedByNetworkCapabilities(
mCapabilityFilter) && acceptRequest(n.request, n.score)) {
+ if (VDBG) log(" needNetworkFor");
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)) {
+ if (VDBG) log(" releaseNetworkFor");
releaseNetworkFor(n.request);
n.requested = false;
+ } else {
+ if (VDBG) log(" done");
}
}
@@ -274,4 +279,12 @@ public class NetworkFactory extends Handler {
protected void log(String s) {
Log.d(LOG_TAG, s);
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - ScoreFilter=").
+ append(mScore).append(", Filter=").append(mCapabilityFilter).append(", requests=").
+ append(mNetworkRequests.size()).append("}");
+ return sb.toString();
+ }
}
diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java
index 36dd2fdfb..a9de23e 100644
--- a/core/java/android/net/NetworkIdentity.java
+++ b/core/java/android/net/NetworkIdentity.java
@@ -25,6 +25,7 @@ import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.telephony.TelephonyManager;
+import android.util.Slog;
import java.util.Objects;
@@ -34,11 +35,17 @@ import java.util.Objects;
*
* @hide
*/
-public class NetworkIdentity {
+public class NetworkIdentity implements Comparable<NetworkIdentity> {
+ private static final String TAG = "NetworkIdentity";
+
/**
* When enabled, combine all {@link #mSubType} together under
* {@link #SUBTYPE_COMBINED}.
+ *
+ * @deprecated we no longer offer to collect statistics on a per-subtype
+ * basis; this is always disabled.
*/
+ @Deprecated
public static final boolean COMBINE_SUBTYPE_ENABLED = true;
public static final int SUBTYPE_COMBINED = -1;
@@ -76,7 +83,7 @@ public class NetworkIdentity {
@Override
public String toString() {
- final StringBuilder builder = new StringBuilder("[");
+ final StringBuilder builder = new StringBuilder("{");
builder.append("type=").append(getNetworkTypeName(mType));
builder.append(", subType=");
if (COMBINE_SUBTYPE_ENABLED) {
@@ -95,7 +102,7 @@ public class NetworkIdentity {
if (mRoaming) {
builder.append(", ROAMING");
}
- return builder.append("]").toString();
+ return builder.append("}").toString();
}
public int getType() {
@@ -133,6 +140,18 @@ public class NetworkIdentity {
}
/**
+ * Scrub given IMSI on production builds.
+ */
+ public static String[] scrubSubscriberId(String[] subscriberId) {
+ if (subscriberId == null) return null;
+ final String[] res = new String[subscriberId.length];
+ for (int i = 0; i < res.length; i++) {
+ res[i] = NetworkIdentity.scrubSubscriberId(subscriberId[i]);
+ }
+ return res;
+ }
+
+ /**
* Build a {@link NetworkIdentity} from the given {@link NetworkState},
* assuming that any mobile networks are using the current IMSI.
*/
@@ -140,23 +159,18 @@ public class NetworkIdentity {
final int type = state.networkInfo.getType();
final int subType = state.networkInfo.getSubtype();
- // TODO: consider moving subscriberId over to LinkCapabilities, so it
- // comes from an authoritative source.
-
String subscriberId = null;
String networkId = null;
boolean roaming = false;
if (isNetworkTypeMobile(type)) {
- final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
- Context.TELEPHONY_SERVICE);
- roaming = telephony.isNetworkRoaming();
- if (state.subscriberId != null) {
- subscriberId = state.subscriberId;
- } else {
- subscriberId = telephony.getSubscriberId();
+ if (state.subscriberId == null) {
+ Slog.w(TAG, "Active mobile network without subscriber!");
}
+ subscriberId = state.subscriberId;
+ roaming = state.networkInfo.isRoaming();
+
} else if (type == TYPE_WIFI) {
if (state.networkId != null) {
networkId = state.networkId;
@@ -170,4 +184,22 @@ public class NetworkIdentity {
return new NetworkIdentity(type, subType, subscriberId, networkId, roaming);
}
+
+ @Override
+ public int compareTo(NetworkIdentity another) {
+ int res = Integer.compare(mType, another.mType);
+ if (res == 0) {
+ res = Integer.compare(mSubType, another.mSubType);
+ }
+ if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) {
+ res = mSubscriberId.compareTo(another.mSubscriberId);
+ }
+ if (res == 0 && mNetworkId != null && another.mNetworkId != null) {
+ res = mNetworkId.compareTo(another.mNetworkId);
+ }
+ if (res == 0) {
+ res = Boolean.compare(mRoaming, another.mRoaming);
+ }
+ return res;
+ }
}
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
index 5d2a43d..b92c9e3 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkMisc.java
@@ -20,15 +20,18 @@ import android.os.Parcel;
import android.os.Parcelable;
/**
- * A grab-bag of information (metadata, policies, properties, etc) about a {@link Network}.
+ * A grab-bag of information (metadata, policies, properties, etc) about a
+ * {@link Network}. Since this contains PII, it should not be sent outside the
+ * system.
*
* @hide
*/
public class NetworkMisc implements Parcelable {
/**
- * If the {@link Network} is a VPN, whether apps are allowed to bypass the VPN. This is set by
- * a {@link VpnService} and used by {@link ConnectivityService} when creating a VPN.
+ * If the {@link Network} is a VPN, whether apps are allowed to bypass the
+ * VPN. This is set by a {@link VpnService} and used by
+ * {@link ConnectivityManager} when creating a VPN.
*/
public boolean allowBypass;
@@ -41,6 +44,11 @@ public class NetworkMisc implements Parcelable {
*/
public boolean explicitlySelected;
+ /**
+ * For mobile networks, this is the subscriber ID (such as IMSI).
+ */
+ public String subscriberId;
+
public NetworkMisc() {
}
@@ -48,6 +56,7 @@ public class NetworkMisc implements Parcelable {
if (nm != null) {
allowBypass = nm.allowBypass;
explicitlySelected = nm.explicitlySelected;
+ subscriberId = nm.subscriberId;
}
}
@@ -60,6 +69,7 @@ public class NetworkMisc implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
out.writeInt(allowBypass ? 1 : 0);
out.writeInt(explicitlySelected ? 1 : 0);
+ out.writeString(subscriberId);
}
public static final Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() {
@@ -68,6 +78,7 @@ public class NetworkMisc implements Parcelable {
NetworkMisc networkMisc = new NetworkMisc();
networkMisc.allowBypass = in.readInt() != 0;
networkMisc.explicitlySelected = in.readInt() != 0;
+ networkMisc.subscriberId = in.readString();
return networkMisc;
}
diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java
index 10c686b..e88bc26 100644
--- a/core/java/android/net/NetworkPolicy.java
+++ b/core/java/android/net/NetworkPolicy.java
@@ -35,7 +35,7 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> {
public static final long LIMIT_DISABLED = -1;
public static final long SNOOZE_NEVER = -1;
- public final NetworkTemplate template;
+ public NetworkTemplate template;
public int cycleDay;
public String cycleTimezone;
public long warningBytes;
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 3f68a44..a939cce 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -41,10 +41,10 @@ import android.os.UserHandle;
* <ul>
* <li>Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission.
* <li>Includes a receiver for {@link #ACTION_SCORE_NETWORKS} guarded by the
- * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission which scores networks
- * and (eventually) calls {@link #updateScores} with the results. If this receiver specifies an
- * android:label attribute, this label will be used when referring to the application throughout
- * system settings; otherwise, the application label will be used.
+ * {@link android.Manifest.permission#BROADCAST_NETWORK_PRIVILEGED} permission which scores
+ * networks and (eventually) calls {@link #updateScores} with the results. If this receiver
+ * specifies an android:label attribute, this label will be used when referring to the
+ * application throughout system settings; otherwise, the application label will be used.
* </ul>
*
* <p>The system keeps track of an active scorer application; at any time, only this application
@@ -192,12 +192,15 @@ public class NetworkScoreManager {
/**
* Set the active scorer to a new package and clear existing scores.
*
+ * <p>Should never be called directly without obtaining user consent. This can be done by using
+ * the {@link #ACTION_CHANGE_ACTIVE} broadcast, or using a custom configuration activity.
+ *
* @return true if the operation succeeded, or false if the new package is not a valid scorer.
* @throws SecurityException if the caller does not hold the
- * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission indicating
- * that it can manage scorer applications.
+ * {@link android.Manifest.permission#SCORE_NETWORKS} permission.
* @hide
*/
+ @SystemApi
public boolean setActiveScorer(String packageName) throws SecurityException {
try {
return mService.setActiveScorer(packageName);
@@ -228,7 +231,7 @@ public class NetworkScoreManager {
*
* @return true if the broadcast was sent, or false if there is no active scorer.
* @throws SecurityException if the caller does not hold the
- * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission.
+ * {@link android.Manifest.permission#BROADCAST_NETWORK_PRIVILEGED} permission.
* @hide
*/
public boolean requestScores(NetworkKey[] networks) throws SecurityException {
@@ -252,7 +255,7 @@ public class NetworkScoreManager {
* @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
* @param scoreCache implementation of {@link INetworkScoreCache} to store the scores.
* @throws SecurityException if the caller does not hold the
- * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission.
+ * {@link android.Manifest.permission#BROADCAST_NETWORK_PRIVILEGED} permission.
* @throws IllegalArgumentException if a score cache is already registered for this type.
* @hide
*/
diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java
index c33f5ec..46f7194 100644
--- a/core/java/android/net/NetworkScorerAppManager.java
+++ b/core/java/android/net/NetworkScorerAppManager.java
@@ -79,7 +79,7 @@ public final class NetworkScorerAppManager {
* <ul>
* <li>Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission.
* <li>Includes a receiver for {@link NetworkScoreManager#ACTION_SCORE_NETWORKS} guarded by the
- * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission.
+ * {@link android.Manifest.permission#BROADCAST_NETWORK_PRIVILEGED} permission.
* </ul>
*
* @return the list of scorers, or the empty list if there are no valid scorers.
@@ -98,8 +98,8 @@ public final class NetworkScorerAppManager {
// Should never happen with queryBroadcastReceivers, but invalid nonetheless.
continue;
}
- if (!permission.BROADCAST_SCORE_NETWORKS.equals(receiverInfo.permission)) {
- // Receiver doesn't require the BROADCAST_SCORE_NETWORKS permission, which means
+ if (!permission.BROADCAST_NETWORK_PRIVILEGED.equals(receiverInfo.permission)) {
+ // Receiver doesn't require the BROADCAST_NETWORK_PRIVILEGED permission, which means
// anyone could trigger network scoring and flood the framework with score requests.
continue;
}
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
index 2e0e9e4..933287f 100644
--- a/core/java/android/net/NetworkState.java
+++ b/core/java/android/net/NetworkState.java
@@ -29,20 +29,17 @@ public class NetworkState implements Parcelable {
public final NetworkInfo networkInfo;
public final LinkProperties linkProperties;
public final NetworkCapabilities networkCapabilities;
- /** Currently only used by testing. */
+ public final Network network;
public final String subscriberId;
public final String networkId;
public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
- NetworkCapabilities networkCapabilities) {
- this(networkInfo, linkProperties, networkCapabilities, null, null);
- }
-
- public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
- NetworkCapabilities networkCapabilities, String subscriberId, String networkId) {
+ NetworkCapabilities networkCapabilities, Network network, String subscriberId,
+ String networkId) {
this.networkInfo = networkInfo;
this.linkProperties = linkProperties;
this.networkCapabilities = networkCapabilities;
+ this.network = network;
this.subscriberId = subscriberId;
this.networkId = networkId;
}
@@ -51,6 +48,7 @@ public class NetworkState implements Parcelable {
networkInfo = in.readParcelable(null);
linkProperties = in.readParcelable(null);
networkCapabilities = in.readParcelable(null);
+ network = in.readParcelable(null);
subscriberId = in.readString();
networkId = in.readString();
}
@@ -65,6 +63,7 @@ public class NetworkState implements Parcelable {
out.writeParcelable(networkInfo, flags);
out.writeParcelable(linkProperties, flags);
out.writeParcelable(networkCapabilities, flags);
+ out.writeParcelable(network, flags);
out.writeString(subscriberId);
out.writeString(networkId);
}
@@ -80,5 +79,4 @@ public class NetworkState implements Parcelable {
return new NetworkState[size];
}
};
-
}
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
deleted file mode 100644
index 35500cc..0000000
--- a/core/java/android/net/NetworkStateTracker.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the 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.Messenger;
-
-import static com.android.internal.util.Protocol.BASE_NETWORK_STATE_TRACKER;
-
-/**
- * Interface provides the {@link com.android.server.ConnectivityService}
- * with three services. Events to the ConnectivityService when
- * changes occur, an API for controlling the network and storage
- * for network specific information.
- *
- * The Connectivity will call startMonitoring before any other
- * method is called.
- *
- * {@hide}
- */
-public interface NetworkStateTracker {
-
- /**
- * -------------------------------------------------------------
- * Event Interface back to ConnectivityService.
- *
- * The events that are to be sent back to the Handler passed
- * to startMonitoring when the particular event occurs.
- * -------------------------------------------------------------
- */
-
- /**
- * The network state has changed and the NetworkInfo object
- * contains the new state.
- *
- * msg.what = EVENT_STATE_CHANGED
- * msg.obj = NetworkInfo object
- */
- public static final int EVENT_STATE_CHANGED = BASE_NETWORK_STATE_TRACKER;
-
- /**
- * msg.what = EVENT_CONFIGURATION_CHANGED
- * msg.obj = NetworkInfo object
- */
- public static final int EVENT_CONFIGURATION_CHANGED = BASE_NETWORK_STATE_TRACKER + 1;
-
- /**
- * msg.what = EVENT_RESTORE_DEFAULT_NETWORK
- * msg.obj = FeatureUser object
- */
- public static final int EVENT_RESTORE_DEFAULT_NETWORK = BASE_NETWORK_STATE_TRACKER + 2;
-
- /**
- * msg.what = EVENT_NETWORK_SUBTYPE_CHANGED
- * msg.obj = NetworkInfo object
- */
- public static final int EVENT_NETWORK_SUBTYPE_CHANGED = BASE_NETWORK_STATE_TRACKER + 3;
-
- /**
- * msg.what = EVENT_NETWORK_CONNECTED
- * msg.obj = LinkProperties object
- */
- public static final int EVENT_NETWORK_CONNECTED = BASE_NETWORK_STATE_TRACKER + 4;
-
- /**
- * msg.what = EVENT_NETWORK_CONNECTION_DISCONNECTED
- * msg.obj = LinkProperties object, same iface name
- */
- public static final int EVENT_NETWORK_DISCONNECTED = BASE_NETWORK_STATE_TRACKER + 5;
-
- /**
- * -------------------------------------------------------------
- * Control Interface
- * -------------------------------------------------------------
- */
- /**
- * Begin monitoring data connectivity.
- *
- * This is the first method called when this interface is used.
- *
- * @param context is the current Android context
- * @param target is the Hander to which to return the events.
- */
- public void startMonitoring(Context context, Handler target);
-
- /**
- * Fetch NetworkInfo for the network
- */
- public NetworkInfo getNetworkInfo();
-
- /**
- * Return the LinkProperties for the connection.
- *
- * @return a copy of the LinkProperties, is never null.
- */
- public LinkProperties getLinkProperties();
-
- /**
- * @return a copy of this connections capabilities, may be empty but never null.
- */
- public NetworkCapabilities getNetworkCapabilities();
-
- /**
- * Get interesting information about this network link
- * @return a copy of link information, null if not available
- */
- public LinkQualityInfo getLinkQualityInfo();
-
- /**
- * Return the system properties name associated with the tcp buffer sizes
- * for this network.
- */
- public String getTcpBufferSizesPropName();
-
- /**
- * Disable connectivity to a network
- * @return {@code true} if a teardown occurred, {@code false} if the
- * teardown did not occur.
- */
- public boolean teardown();
-
- /**
- * Reenable connectivity to a network after a {@link #teardown()}.
- * @return {@code true} if we're connected or expect to be connected
- */
- public boolean reconnect();
-
- /**
- * Captive portal check has completed
- */
- public void captivePortalCheckCompleted(boolean isCaptive);
-
- /**
- * Turn the wireless radio off for a network.
- * @param turnOn {@code true} to turn the radio on, {@code false}
- */
- public boolean setRadio(boolean turnOn);
-
- /**
- * Returns an indication of whether this network is available for
- * connections. A value of {@code false} means that some quasi-permanent
- * condition prevents connectivity to this network.
- *
- * NOTE that this is broken on multi-connection devices. Should be fixed in J release
- * TODO - fix on multi-pdp devices
- */
- public boolean isAvailable();
-
- /**
- * User control of data connection through this network, typically persisted
- * internally.
- */
- public void setUserDataEnable(boolean enabled);
-
- /**
- * Policy control of data connection through this network, typically not
- * persisted internally. Usually used when {@link NetworkPolicy#limitBytes}
- * is passed.
- */
- public void setPolicyDataEnable(boolean enabled);
-
- /**
- * -------------------------------------------------------------
- * Storage API used by ConnectivityService for saving
- * Network specific information.
- * -------------------------------------------------------------
- */
-
- /**
- * Check if private DNS route is set for the network
- */
- public boolean isPrivateDnsRouteSet();
-
- /**
- * Set a flag indicating private DNS route is set
- */
- public void privateDnsRouteSet(boolean enabled);
-
- /**
- * Check if default route is set
- */
- public boolean isDefaultRouteSet();
-
- /**
- * Set a flag indicating default route is set for the network
- */
- public void defaultRouteSet(boolean enabled);
-
- /**
- * Check if tear down was requested
- */
- public boolean isTeardownRequested();
-
- /**
- * Indicate tear down requested from connectivity
- */
- public void setTeardownRequested(boolean isRequested);
-
- /**
- * An external dependency has been met/unmet
- */
- public void setDependencyMet(boolean met);
-
- /**
- * Informs the state tracker that another interface is stacked on top of it.
- **/
- public void addStackedLink(LinkProperties link);
-
- /**
- * Informs the state tracker that a stacked interface has been removed.
- **/
- public void removeStackedLink(LinkProperties link);
-
- /*
- * Called once to setup async channel between this and
- * the underlying network specific code.
- */
- public void supplyMessenger(Messenger messenger);
-
- /*
- * Network interface name that we'll lookup for sampling data
- */
- public String getNetworkInterfaceName();
-
- /*
- * Save the starting sample
- */
- public void startSampling(SamplingDataTracker.SamplingSnapshot s);
-
- /*
- * Save the ending sample
- */
- 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/NetworkStats.java b/core/java/android/net/NetworkStats.java
index ea5dfd1..2afe578 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -733,6 +733,22 @@ public class NetworkStats implements Parcelable {
}
/**
+ * Return text description of {@link #set} value.
+ */
+ public static String setToCheckinString(int set) {
+ switch (set) {
+ case SET_ALL:
+ return "all";
+ case SET_DEFAULT:
+ return "def";
+ case SET_FOREGROUND:
+ return "fg";
+ default:
+ return "unk";
+ }
+ }
+
+ /**
* Return text description of {@link #tag} value.
*/
public static String tagToString(int tag) {
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 62d8738..4a4accb 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -26,6 +26,7 @@ import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray;
import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray;
import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import static com.android.internal.util.ArrayUtils.total;
import android.os.Parcel;
@@ -38,6 +39,7 @@ import java.io.CharArrayWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
+import java.io.PrintWriter;
import java.net.ProtocolException;
import java.util.Arrays;
import java.util.Random;
@@ -573,8 +575,22 @@ public class NetworkStatsHistory implements Parcelable {
return (long) (start + (r.nextFloat() * (end - start)));
}
+ /**
+ * Quickly determine if this history intersects with given window.
+ */
+ public boolean intersects(long start, long end) {
+ final long dataStart = getStart();
+ final long dataEnd = getEnd();
+ if (start >= dataStart && start <= dataEnd) return true;
+ if (end >= dataStart && end <= dataEnd) return true;
+ if (dataStart >= start && dataStart <= end) return true;
+ if (dataEnd >= start && dataEnd <= end) return true;
+ return false;
+ }
+
public void dump(IndentingPrintWriter pw, boolean fullHistory) {
- pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration);
+ pw.print("NetworkStatsHistory: bucketDuration=");
+ pw.println(bucketDuration / SECOND_IN_MILLIS);
pw.increaseIndent();
final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32);
@@ -583,19 +599,35 @@ public class NetworkStatsHistory implements Parcelable {
}
for (int i = start; i < bucketCount; i++) {
- pw.print("bucketStart="); pw.print(bucketStart[i]);
- if (activeTime != null) { pw.print(" activeTime="); pw.print(activeTime[i]); }
- if (rxBytes != null) { pw.print(" rxBytes="); pw.print(rxBytes[i]); }
- if (rxPackets != null) { pw.print(" rxPackets="); pw.print(rxPackets[i]); }
- if (txBytes != null) { pw.print(" txBytes="); pw.print(txBytes[i]); }
- if (txPackets != null) { pw.print(" txPackets="); pw.print(txPackets[i]); }
- if (operations != null) { pw.print(" operations="); pw.print(operations[i]); }
+ pw.print("st="); pw.print(bucketStart[i] / SECOND_IN_MILLIS);
+ if (rxBytes != null) { pw.print(" rb="); pw.print(rxBytes[i]); }
+ if (rxPackets != null) { pw.print(" rp="); pw.print(rxPackets[i]); }
+ if (txBytes != null) { pw.print(" tb="); pw.print(txBytes[i]); }
+ if (txPackets != null) { pw.print(" tp="); pw.print(txPackets[i]); }
+ if (operations != null) { pw.print(" op="); pw.print(operations[i]); }
pw.println();
}
pw.decreaseIndent();
}
+ public void dumpCheckin(PrintWriter pw) {
+ pw.print("d,");
+ pw.print(bucketDuration / SECOND_IN_MILLIS);
+ pw.println();
+
+ for (int i = 0; i < bucketCount; i++) {
+ pw.print("b,");
+ pw.print(bucketStart[i] / SECOND_IN_MILLIS); pw.print(',');
+ if (rxBytes != null) { pw.print(rxBytes[i]); } else { pw.print("*"); } pw.print(',');
+ if (rxPackets != null) { pw.print(rxPackets[i]); } else { pw.print("*"); } pw.print(',');
+ if (txBytes != null) { pw.print(txBytes[i]); } else { pw.print("*"); } pw.print(',');
+ if (txPackets != null) { pw.print(txPackets[i]); } else { pw.print("*"); } pw.print(',');
+ if (operations != null) { pw.print(operations[i]); } else { pw.print("*"); }
+ pw.println();
+ }
+ }
+
@Override
public String toString() {
final CharArrayWriter writer = new CharArrayWriter();
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 27197cc..57eef83 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -16,12 +16,12 @@
package android.net;
+import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.NetworkIdentity.COMBINE_SUBTYPE_ENABLED;
-import static android.net.NetworkIdentity.scrubSubscriberId;
import static android.net.wifi.WifiInfo.removeDoubleQuotes;
import static android.telephony.TelephonyManager.NETWORK_CLASS_2_G;
import static android.telephony.TelephonyManager.NETWORK_CLASS_3_G;
@@ -34,9 +34,11 @@ import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.Objects;
-
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.Objects;
/**
* Template definition used to generically match {@link NetworkIdentity},
@@ -47,12 +49,15 @@ import com.android.internal.annotations.VisibleForTesting;
public class NetworkTemplate implements Parcelable {
public static final int MATCH_MOBILE_ALL = 1;
+ @Deprecated
public static final int MATCH_MOBILE_3G_LOWER = 2;
+ @Deprecated
public static final int MATCH_MOBILE_4G = 3;
public static final int MATCH_WIFI = 4;
public static final int MATCH_ETHERNET = 5;
public static final int MATCH_MOBILE_WILDCARD = 6;
public static final int MATCH_WIFI_WILDCARD = 7;
+ public static final int MATCH_BLUETOOTH = 8;
/**
* Set of {@link NetworkInfo#getType()} that reflect data usage.
@@ -134,19 +139,45 @@ public class NetworkTemplate implements Parcelable {
return new NetworkTemplate(MATCH_ETHERNET, null, null);
}
+ /**
+ * Template to combine all {@link ConnectivityManager#TYPE_BLUETOOTH} style
+ * networks together.
+ */
+ public static NetworkTemplate buildTemplateBluetooth() {
+ return new NetworkTemplate(MATCH_BLUETOOTH, null, null);
+ }
+
private final int mMatchRule;
private final String mSubscriberId;
+
+ /**
+ * Ugh, templates are designed to target a single subscriber, but we might
+ * need to match several "merged" subscribers. These are the subscribers
+ * that should be considered to match this template.
+ * <p>
+ * Since the merge set is dynamic, it should <em>not</em> be persisted or
+ * used for determining equality.
+ */
+ private final String[] mMatchSubscriberIds;
+
private final String mNetworkId;
public NetworkTemplate(int matchRule, String subscriberId, String networkId) {
+ this(matchRule, subscriberId, new String[] { subscriberId }, networkId);
+ }
+
+ public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
+ String networkId) {
mMatchRule = matchRule;
mSubscriberId = subscriberId;
+ mMatchSubscriberIds = matchSubscriberIds;
mNetworkId = networkId;
}
private NetworkTemplate(Parcel in) {
mMatchRule = in.readInt();
mSubscriberId = in.readString();
+ mMatchSubscriberIds = in.createStringArray();
mNetworkId = in.readString();
}
@@ -154,6 +185,7 @@ public class NetworkTemplate implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mMatchRule);
dest.writeString(mSubscriberId);
+ dest.writeStringArray(mMatchSubscriberIds);
dest.writeString(mNetworkId);
}
@@ -167,7 +199,12 @@ public class NetworkTemplate implements Parcelable {
final StringBuilder builder = new StringBuilder("NetworkTemplate: ");
builder.append("matchRule=").append(getMatchRuleName(mMatchRule));
if (mSubscriberId != null) {
- builder.append(", subscriberId=").append(scrubSubscriberId(mSubscriberId));
+ builder.append(", subscriberId=").append(
+ NetworkIdentity.scrubSubscriberId(mSubscriberId));
+ }
+ if (mMatchSubscriberIds != null) {
+ builder.append(", matchSubscriberIds=").append(
+ Arrays.toString(NetworkIdentity.scrubSubscriberId(mMatchSubscriberIds)));
}
if (mNetworkId != null) {
builder.append(", networkId=").append(mNetworkId);
@@ -191,6 +228,18 @@ public class NetworkTemplate implements Parcelable {
return false;
}
+ public boolean isMatchRuleMobile() {
+ switch (mMatchRule) {
+ case MATCH_MOBILE_3G_LOWER:
+ case MATCH_MOBILE_4G:
+ case MATCH_MOBILE_ALL:
+ case MATCH_MOBILE_WILDCARD:
+ return true;
+ default:
+ return false;
+ }
+ }
+
public int getMatchRule() {
return mMatchRule;
}
@@ -222,6 +271,8 @@ public class NetworkTemplate implements Parcelable {
return matchesMobileWildcard(ident);
case MATCH_WIFI_WILDCARD:
return matchesWifiWildcard(ident);
+ case MATCH_BLUETOOTH:
+ return matchesBluetooth(ident);
default:
throw new IllegalArgumentException("unknown network template");
}
@@ -235,14 +286,16 @@ public class NetworkTemplate implements Parcelable {
// TODO: consider matching against WiMAX subscriber identity
return true;
} else {
- return ((sForceAllNetworkTypes || contains(DATA_USAGE_NETWORK_TYPES, ident.mType))
- && Objects.equals(mSubscriberId, ident.mSubscriberId));
+ final boolean matchesType = (sForceAllNetworkTypes
+ || contains(DATA_USAGE_NETWORK_TYPES, ident.mType));
+ return matchesType && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId);
}
}
/**
* Check if mobile network classified 3G or lower with matching IMSI.
*/
+ @Deprecated
private boolean matchesMobile3gLower(NetworkIdentity ident) {
ensureSubtypeAvailable();
if (ident.mType == TYPE_WIMAX) {
@@ -261,6 +314,7 @@ public class NetworkTemplate implements Parcelable {
/**
* Check if mobile network classified 4G with matching IMSI.
*/
+ @Deprecated
private boolean matchesMobile4g(NetworkIdentity ident) {
ensureSubtypeAvailable();
if (ident.mType == TYPE_WIMAX) {
@@ -316,6 +370,16 @@ public class NetworkTemplate implements Parcelable {
}
}
+ /**
+ * Check if matches Bluetooth network template.
+ */
+ private boolean matchesBluetooth(NetworkIdentity ident) {
+ if (ident.mType == TYPE_BLUETOOTH) {
+ return true;
+ }
+ return false;
+ }
+
private static String getMatchRuleName(int matchRule) {
switch (matchRule) {
case MATCH_MOBILE_3G_LOWER:
@@ -332,6 +396,8 @@ public class NetworkTemplate implements Parcelable {
return "MOBILE_WILDCARD";
case MATCH_WIFI_WILDCARD:
return "WIFI_WILDCARD";
+ case MATCH_BLUETOOTH:
+ return "BLUETOOTH";
default:
return "UNKNOWN";
}
@@ -344,6 +410,27 @@ public class NetworkTemplate implements Parcelable {
}
}
+ /**
+ * Examine the given template and normalize if it refers to a "merged"
+ * mobile subscriber. We pick the "lowest" merged subscriber as the primary
+ * for key purposes, and expand the template to match all other merged
+ * subscribers.
+ * <p>
+ * For example, given an incoming template matching B, and the currently
+ * active merge set [A,B], we'd return a new template that primarily matches
+ * A, but also matches B.
+ */
+ public static NetworkTemplate normalize(NetworkTemplate template, String[] merged) {
+ if (template.isMatchRuleMobile() && ArrayUtils.contains(merged, template.mSubscriberId)) {
+ // Requested template subscriber is part of the merge group; return
+ // a template that matches all merged subscribers.
+ return new NetworkTemplate(template.mMatchRule, merged[0], merged,
+ template.mNetworkId);
+ } else {
+ return template;
+ }
+ }
+
public static final Creator<NetworkTemplate> CREATOR = new Creator<NetworkTemplate>() {
@Override
public NetworkTemplate createFromParcel(Parcel in) {
diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java
index bc1e4d5..17a84a7 100644
--- a/core/java/android/net/Proxy.java
+++ b/core/java/android/net/Proxy.java
@@ -19,7 +19,6 @@ package android.net;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
-import android.net.ProxyInfo;
import android.text.TextUtils;
import android.util.Log;
diff --git a/core/java/android/net/ProxyDataTracker.java b/core/java/android/net/ProxyDataTracker.java
deleted file mode 100644
index 7d23125..0000000
--- a/core/java/android/net/ProxyDataTracker.java
+++ /dev/null
@@ -1,207 +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.net;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Log;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * A data tracker responsible for bringing up and tearing down the system proxy server.
- *
- * {@hide}
- */
-public class ProxyDataTracker extends BaseNetworkStateTracker {
- private static final String TAG = "ProxyDataTracker";
- private static final String NETWORK_TYPE = "PROXY";
-
- // TODO: investigate how to get these DNS addresses from the system.
- private static final String DNS1 = "8.8.8.8";
- private static final String DNS2 = "8.8.4.4";
- private static final String INTERFACE_NAME = "ifb0";
- private static final String REASON_ENABLED = "enabled";
- private static final String REASON_DISABLED = "disabled";
- private static final String REASON_PROXY_DOWN = "proxy_down";
-
- private static final int MSG_TEAR_DOWN_REQUEST = 1;
- private static final int MSG_SETUP_REQUEST = 2;
-
- private static final String PERMISSION_PROXY_STATUS_SENDER =
- "android.permission.ACCESS_NETWORK_CONDITIONS";
- private static final String ACTION_PROXY_STATUS_CHANGE =
- "com.android.net.PROXY_STATUS_CHANGE";
- private static final String KEY_IS_PROXY_AVAILABLE = "is_proxy_available";
- private static final String KEY_REPLY_TO_MESSENGER_BINDER = "reply_to_messenger_binder";
- private static final String KEY_REPLY_TO_MESSENGER_BINDER_BUNDLE =
- "reply_to_messenger_binder_bundle";
-
- private Handler mTarget;
- private Messenger mProxyStatusService;
- private AtomicBoolean mReconnectRequested = new AtomicBoolean(false);
- private AtomicBoolean mIsProxyAvailable = new AtomicBoolean(false);
- private final AtomicInteger mDefaultGatewayAddr = new AtomicInteger(0);
-
- private final BroadcastReceiver mProxyStatusServiceListener = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(ACTION_PROXY_STATUS_CHANGE)) {
- mIsProxyAvailable.set(intent.getBooleanExtra(KEY_IS_PROXY_AVAILABLE, false));
- if (mIsProxyAvailable.get()) {
- Bundle bundle = intent.getBundleExtra(KEY_REPLY_TO_MESSENGER_BINDER_BUNDLE);
- if (bundle == null || bundle.getBinder(KEY_REPLY_TO_MESSENGER_BINDER) == null) {
- Log.e(TAG, "no messenger binder in the intent to send future requests");
- mIsProxyAvailable.set(false);
- return;
- }
- mProxyStatusService =
- new Messenger(bundle.getBinder(KEY_REPLY_TO_MESSENGER_BINDER));
- // If there is a pending reconnect request, do it now.
- if (mReconnectRequested.get()) {
- reconnect();
- }
- } else {
- setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
- REASON_PROXY_DOWN, null);
- }
- } else {
- Log.d(TAG, "Unrecognized broadcast intent");
- }
- }
- };
-
- /**
- * Create a new ProxyDataTracker
- */
- public ProxyDataTracker() {
- mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_PROXY, 0, NETWORK_TYPE, "");
- mLinkProperties = new LinkProperties();
- mNetworkCapabilities = new NetworkCapabilities();
- mNetworkInfo.setIsAvailable(true);
- try {
- mLinkProperties.addDnsServer(InetAddress.getByName(DNS1));
- mLinkProperties.addDnsServer(InetAddress.getByName(DNS2));
- mLinkProperties.setInterfaceName(INTERFACE_NAME);
- } catch (UnknownHostException e) {
- Log.e(TAG, "Could not add DNS address", e);
- }
- }
-
- @Override
- public Object clone() throws CloneNotSupportedException {
- throw new CloneNotSupportedException();
- }
-
- @Override
- public void startMonitoring(Context context, Handler target) {
- mContext = context;
- mTarget = target;
- mContext.registerReceiver(mProxyStatusServiceListener,
- new IntentFilter(ACTION_PROXY_STATUS_CHANGE),
- PERMISSION_PROXY_STATUS_SENDER,
- null);
- }
-
- /**
- * Disable connectivity to the network.
- */
- public boolean teardown() {
- setTeardownRequested(true);
- mReconnectRequested.set(false);
- try {
- if (mIsProxyAvailable.get() && mProxyStatusService != null) {
- mProxyStatusService.send(Message.obtain(null, MSG_TEAR_DOWN_REQUEST));
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to connect to proxy status service", e);
- return false;
- }
- setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, REASON_DISABLED, null);
- return true;
- }
-
- /**
- * Re-enable proxy data connectivity after a {@link #teardown()}.
- */
- public boolean reconnect() {
- mReconnectRequested.set(true);
- setTeardownRequested(false);
- if (!mIsProxyAvailable.get()) {
- Log.w(TAG, "Reconnect requested even though proxy service is not up. Bailing.");
- return false;
- }
- setDetailedState(NetworkInfo.DetailedState.CONNECTING, REASON_ENABLED, null);
-
- try {
- mProxyStatusService.send(Message.obtain(null, MSG_SETUP_REQUEST));
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to connect to proxy status service", e);
- setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, REASON_PROXY_DOWN, null);
- return false;
- }
- // We'll assume proxy is set up successfully. If not, a status change broadcast will be
- // received afterwards to indicate any failure.
- setDetailedState(NetworkInfo.DetailedState.CONNECTED, REASON_ENABLED, null);
- return true;
- }
-
- /**
- * Fetch default gateway address for the network
- */
- public int getDefaultGatewayAddr() {
- return mDefaultGatewayAddr.get();
- }
-
- /**
- * Return the system properties name associated with the tcp buffer sizes
- * for this network.
- */
- public String getTcpBufferSizesPropName() {
- return "net.tcp.buffersize.wifi";
- }
-
- /**
- * Record the detailed state of a network, and if it is a
- * change from the previous state, send a notification to
- * any listeners.
- * @param state the new @{code DetailedState}
- * @param reason a {@code String} indicating a reason for the state change,
- * if one was supplied. May be {@code null}.
- * @param extraInfo optional {@code String} providing extra information about the state change
- */
- private void setDetailedState(NetworkInfo.DetailedState state, String reason,
- String extraInfo) {
- mNetworkInfo.setDetailedState(state, reason, extraInfo);
- Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
- msg.sendToTarget();
- }
-}
diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java
index 1534e2c..a3cad77 100644
--- a/core/java/android/net/ProxyInfo.java
+++ b/core/java/android/net/ProxyInfo.java
@@ -36,7 +36,13 @@ import java.util.Locale;
*
* Other HTTP stacks will need to obtain the proxy info from
* {@link Proxy#PROXY_CHANGE_ACTION} broadcast as the extra {@link Proxy#EXTRA_PROXY_INFO}.
+ *
+ * @deprecated Please use {@link java.net.URL#openConnection}, {@link java.net.Proxy} and
+ * friends. The Apache HTTP client is no longer maintained and may be removed in a future
+ * release. Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
*/
+@Deprecated
public class ProxyInfo implements Parcelable {
private String mHost;
@@ -254,7 +260,8 @@ public class ProxyInfo implements Parcelable {
if (!Uri.EMPTY.equals(mPacFileUrl)) {
sb.append("PAC Script: ");
sb.append(mPacFileUrl);
- } else if (mHost != null) {
+ }
+ if (mHost != null) {
sb.append("[");
sb.append(mHost);
sb.append("] ");
diff --git a/core/java/android/net/PskKeyManager.java b/core/java/android/net/PskKeyManager.java
index d162282..f82e635 100644
--- a/core/java/android/net/PskKeyManager.java
+++ b/core/java/android/net/PskKeyManager.java
@@ -81,6 +81,13 @@ import javax.net.ssl.SSLEngine;
* Subclasses should normally provide their own implementation of {@code getKey} because the default
* implementation returns no key, which aborts the handshake.
*
+ * <h3>Known issues</h3>
+ * The implementation of {@code ECDHE_PSK} cipher suites in API Level 21 contains a bug which breaks
+ * compatibility with other implementations. {@code ECDHE_PSK} cipher suites are enabled by default
+ * on platforms with API Level 21 when an {@code SSLContext} is initialized with a
+ * {@code PskKeyManager}. A workaround is to disable {@code ECDHE_PSK} cipher suites on platforms
+ * with API Level 21.
+ *
* <h3>Example</h3>
* The following example illustrates how to create an {@code SSLContext} which enables the use of
* TLS-PSK in {@code SSLSocket}, {@code SSLServerSocket} and {@code SSLEngine} instances obtained
diff --git a/core/java/android/net/RssiCurve.java b/core/java/android/net/RssiCurve.java
index f653f37..8ebe9e8 100644
--- a/core/java/android/net/RssiCurve.java
+++ b/core/java/android/net/RssiCurve.java
@@ -27,8 +27,8 @@ import java.util.Objects;
* A curve defining the network score over a range of RSSI values.
*
* <p>For each RSSI bucket, the score may be any byte. Scores have no absolute meaning and are only
- * considered relative to other scores assigned by the same scorer. Networks with no score are all
- * considered equivalent and ranked below any network with a score.
+ * considered relative to other scores assigned by the same scorer. Networks with no score are
+ * treated equivalently to a network with score {@link Byte#MIN_VALUE}, and will not be used.
*
* <p>For example, consider a curve starting at -110 dBm with a bucket width of 10 and the
* following buckets: {@code [-20, -10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]}.
@@ -52,6 +52,7 @@ import java.util.Objects;
*/
@SystemApi
public class RssiCurve implements Parcelable {
+ private static final int DEFAULT_ACTIVE_NETWORK_RSSI_BOOST = 25;
/** The starting dBm of the curve. */
public final int start;
@@ -63,6 +64,15 @@ public class RssiCurve implements Parcelable {
public final byte[] rssiBuckets;
/**
+ * The RSSI boost to give this network when active, in dBm.
+ *
+ * <p>When the system is connected to this network, it will pretend that the network has this
+ * much higher of an RSSI. This is to avoid switching networks when another network has only a
+ * slightly higher score.
+ */
+ public final int activeNetworkRssiBoost;
+
+ /**
* Construct a new {@link RssiCurve}.
*
* @param start the starting dBm of the curve.
@@ -70,12 +80,25 @@ public class RssiCurve implements Parcelable {
* @param rssiBuckets the score for each RSSI bucket.
*/
public RssiCurve(int start, int bucketWidth, byte[] rssiBuckets) {
+ this(start, bucketWidth, rssiBuckets, DEFAULT_ACTIVE_NETWORK_RSSI_BOOST);
+ }
+
+ /**
+ * Construct a new {@link RssiCurve}.
+ *
+ * @param start the starting dBm of the curve.
+ * @param bucketWidth the width of each RSSI bucket, in dBm.
+ * @param rssiBuckets the score for each RSSI bucket.
+ * @param activeNetworkRssiBoost the RSSI boost to apply when this network is active, in dBm.
+ */
+ public RssiCurve(int start, int bucketWidth, byte[] rssiBuckets, int activeNetworkRssiBoost) {
this.start = start;
this.bucketWidth = bucketWidth;
if (rssiBuckets == null || rssiBuckets.length == 0) {
throw new IllegalArgumentException("rssiBuckets must be at least one element large.");
}
this.rssiBuckets = rssiBuckets;
+ this.activeNetworkRssiBoost = activeNetworkRssiBoost;
}
private RssiCurve(Parcel in) {
@@ -84,6 +107,7 @@ public class RssiCurve implements Parcelable {
int bucketCount = in.readInt();
rssiBuckets = new byte[bucketCount];
in.readByteArray(rssiBuckets);
+ activeNetworkRssiBoost = in.readInt();
}
@Override
@@ -97,6 +121,7 @@ public class RssiCurve implements Parcelable {
out.writeInt(bucketWidth);
out.writeInt(rssiBuckets.length);
out.writeByteArray(rssiBuckets);
+ out.writeInt(activeNetworkRssiBoost);
}
/**
@@ -108,6 +133,23 @@ public class RssiCurve implements Parcelable {
* @return the score for the given RSSI.
*/
public byte lookupScore(int rssi) {
+ return lookupScore(rssi, false /* isActiveNetwork */);
+ }
+
+ /**
+ * Lookup the score for a given RSSI value.
+ *
+ * @param rssi The RSSI to lookup. If the RSSI falls below the start of the curve, the score at
+ * the start of the curve will be returned. If it falls after the end of the curve, the
+ * score at the end of the curve will be returned.
+ * @param isActiveNetwork Whether this network is currently active.
+ * @return the score for the given RSSI.
+ */
+ public byte lookupScore(int rssi, boolean isActiveNetwork) {
+ if (isActiveNetwork) {
+ rssi += activeNetworkRssiBoost;
+ }
+
int index = (rssi - start) / bucketWidth;
// Snap the index to the closest bucket if it falls outside the curve.
@@ -136,12 +178,13 @@ public class RssiCurve implements Parcelable {
return start == rssiCurve.start &&
bucketWidth == rssiCurve.bucketWidth &&
- Arrays.equals(rssiBuckets, rssiCurve.rssiBuckets);
+ Arrays.equals(rssiBuckets, rssiCurve.rssiBuckets) &&
+ activeNetworkRssiBoost == rssiCurve.activeNetworkRssiBoost;
}
@Override
public int hashCode() {
- return Objects.hash(start, bucketWidth, rssiBuckets);
+ return Objects.hash(start, bucketWidth, rssiBuckets, activeNetworkRssiBoost);
}
@Override
@@ -150,7 +193,9 @@ public class RssiCurve implements Parcelable {
sb.append("RssiCurve[start=")
.append(start)
.append(",bucketWidth=")
- .append(bucketWidth);
+ .append(bucketWidth)
+ .append(",activeNetworkRssiBoost=")
+ .append(activeNetworkRssiBoost);
sb.append(",buckets=");
for (int i = 0; i < rssiBuckets.length; i++) {
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 69be4fd..6654577 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -154,7 +154,13 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
* for none. The socket timeout is reset to 0 after the handshake.
* @param cache The {@link SSLSessionCache} to use, or null for no cache.
* @return a new SocketFactory with the specified parameters
+ *
+ * @deprecated Use {@link #getDefault()} along with a {@link javax.net.ssl.HttpsURLConnection}
+ * instead. The Apache HTTP client is no longer maintained and may be removed in a future
+ * release. Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
*/
+ @Deprecated
public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(
int handshakeTimeoutMillis, SSLSessionCache cache) {
return new org.apache.http.conn.ssl.SSLSocketFactory(
diff --git a/core/java/android/net/SamplingDataTracker.java b/core/java/android/net/SamplingDataTracker.java
deleted file mode 100644
index acd56f2..0000000
--- a/core/java/android/net/SamplingDataTracker.java
+++ /dev/null
@@ -1,300 +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.net;
-
-
-import android.os.SystemClock;
-import android.util.Slog;
-
-import java.io.BufferedReader;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * @hide
- */
-public class SamplingDataTracker
-{
- private static final boolean DBG = false;
- private static final String TAG = "SamplingDataTracker";
-
- public static class SamplingSnapshot
- {
- public long mTxByteCount;
- public long mRxByteCount;
- public long mTxPacketCount;
- public long mRxPacketCount;
- public long mTxPacketErrorCount;
- public long mRxPacketErrorCount;
- public long mTimestamp;
- }
-
- public static void getSamplingSnapshots(Map<String, SamplingSnapshot> mapIfaceToSample) {
-
- BufferedReader reader = null;
- try {
- reader = new BufferedReader(new FileReader("/proc/net/dev"));
-
- // Skip over the line bearing column titles (there are 2 lines)
- String line;
- reader.readLine();
- reader.readLine();
-
- while ((line = reader.readLine()) != null) {
-
- // remove leading whitespace
- line = line.trim();
-
- String[] tokens = line.split("[ ]+");
- if (tokens.length < 17) {
- continue;
- }
-
- /* column format is
- * Interface (Recv)bytes packets errs drop fifo frame compressed multicast \
- * (Transmit)bytes packets errs drop fifo colls carrier compress
- */
-
- String currentIface = tokens[0].split(":")[0];
- if (DBG) Slog.d(TAG, "Found data for interface " + currentIface);
- if (mapIfaceToSample.containsKey(currentIface)) {
-
- try {
- SamplingSnapshot ss = new SamplingSnapshot();
-
- ss.mTxByteCount = Long.parseLong(tokens[1]);
- ss.mTxPacketCount = Long.parseLong(tokens[2]);
- ss.mTxPacketErrorCount = Long.parseLong(tokens[3]);
- ss.mRxByteCount = Long.parseLong(tokens[9]);
- ss.mRxPacketCount = Long.parseLong(tokens[10]);
- ss.mRxPacketErrorCount = Long.parseLong(tokens[11]);
-
- ss.mTimestamp = SystemClock.elapsedRealtime();
-
- if (DBG) {
- Slog.d(TAG, "Interface = " + currentIface);
- Slog.d(TAG, "ByteCount = " + String.valueOf(ss.mTxByteCount));
- Slog.d(TAG, "TxPacketCount = " + String.valueOf(ss.mTxPacketCount));
- Slog.d(TAG, "TxPacketErrorCount = "
- + String.valueOf(ss.mTxPacketErrorCount));
- Slog.d(TAG, "RxByteCount = " + String.valueOf(ss.mRxByteCount));
- Slog.d(TAG, "RxPacketCount = " + String.valueOf(ss.mRxPacketCount));
- Slog.d(TAG, "RxPacketErrorCount = "
- + String.valueOf(ss.mRxPacketErrorCount));
- Slog.d(TAG, "Timestamp = " + String.valueOf(ss.mTimestamp));
- Slog.d(TAG, "---------------------------");
- }
-
- mapIfaceToSample.put(currentIface, ss);
-
- } catch (NumberFormatException e) {
- // just ignore this data point
- }
- }
- }
-
- if (DBG) {
- Iterator it = mapIfaceToSample.entrySet().iterator();
- while (it.hasNext()) {
- Map.Entry kvpair = (Map.Entry)it.next();
- if (kvpair.getValue() == null) {
- Slog.d(TAG, "could not find snapshot for interface " + kvpair.getKey());
- }
- }
- }
- } catch(FileNotFoundException e) {
- Slog.e(TAG, "could not find /proc/net/dev");
- } catch (IOException e) {
- Slog.e(TAG, "could not read /proc/net/dev");
- } finally {
- try {
- if (reader != null) {
- reader.close();
- }
- } catch (IOException e) {
- Slog.e(TAG, "could not close /proc/net/dev");
- }
- }
- }
-
- // Snapshots from previous sampling interval
- private SamplingSnapshot mBeginningSample;
- private SamplingSnapshot mEndingSample;
-
- // Starting snapshot of current interval
- private SamplingSnapshot mLastSample;
-
- // Protects sampling data from concurrent access
- public final Object mSamplingDataLock = new Object();
-
- // We need long enough time for a good sample
- private final int MINIMUM_SAMPLING_INTERVAL = 15 * 1000;
-
- // statistics is useless unless we have enough data
- private final int MINIMUM_SAMPLED_PACKETS = 30;
-
- public void startSampling(SamplingSnapshot s) {
- synchronized(mSamplingDataLock) {
- mLastSample = s;
- }
- }
-
- public void stopSampling(SamplingSnapshot s) {
- synchronized(mSamplingDataLock) {
- if (mLastSample != null) {
- if (s.mTimestamp - mLastSample.mTimestamp > MINIMUM_SAMPLING_INTERVAL
- && getSampledPacketCount(mLastSample, s) > MINIMUM_SAMPLED_PACKETS) {
- mBeginningSample = mLastSample;
- mEndingSample = s;
- mLastSample = null;
- } else {
- if (DBG) Slog.d(TAG, "Throwing current sample away because it is too small");
- }
- }
- }
- }
-
- public void resetSamplingData() {
- if (DBG) Slog.d(TAG, "Resetting sampled network data");
- synchronized(mSamplingDataLock) {
-
- // We could just take another sample here and treat it as an
- // 'ending sample' effectively shortening sampling interval, but that
- // requires extra work (specifically, reading the sample needs to be
- // done asynchronously)
-
- mLastSample = null;
- }
- }
-
- public long getSampledTxByteCount() {
- synchronized(mSamplingDataLock) {
- if (mBeginningSample != null && mEndingSample != null) {
- return mEndingSample.mTxByteCount - mBeginningSample.mTxByteCount;
- } else {
- return LinkQualityInfo.UNKNOWN_LONG;
- }
- }
- }
-
- public long getSampledTxPacketCount() {
- synchronized(mSamplingDataLock) {
- if (mBeginningSample != null && mEndingSample != null) {
- return mEndingSample.mTxPacketCount - mBeginningSample.mTxPacketCount;
- } else {
- return LinkQualityInfo.UNKNOWN_LONG;
- }
- }
- }
-
- public long getSampledTxPacketErrorCount() {
- synchronized(mSamplingDataLock) {
- if (mBeginningSample != null && mEndingSample != null) {
- return mEndingSample.mTxPacketErrorCount - mBeginningSample.mTxPacketErrorCount;
- } else {
- return LinkQualityInfo.UNKNOWN_LONG;
- }
- }
- }
-
- public long getSampledRxByteCount() {
- synchronized(mSamplingDataLock) {
- if (mBeginningSample != null && mEndingSample != null) {
- return mEndingSample.mRxByteCount - mBeginningSample.mRxByteCount;
- } else {
- return LinkQualityInfo.UNKNOWN_LONG;
- }
- }
- }
-
- public long getSampledRxPacketCount() {
- synchronized(mSamplingDataLock) {
- if (mBeginningSample != null && mEndingSample != null) {
- return mEndingSample.mRxPacketCount - mBeginningSample.mRxPacketCount;
- } else {
- return LinkQualityInfo.UNKNOWN_LONG;
- }
- }
- }
-
- public long getSampledPacketCount() {
- return getSampledPacketCount(mBeginningSample, mEndingSample);
- }
-
- public long getSampledPacketCount(SamplingSnapshot begin, SamplingSnapshot end) {
- if (begin != null && end != null) {
- long rxPacketCount = end.mRxPacketCount - begin.mRxPacketCount;
- long txPacketCount = end.mTxPacketCount - begin.mTxPacketCount;
- return rxPacketCount + txPacketCount;
- } else {
- return LinkQualityInfo.UNKNOWN_LONG;
- }
- }
-
- public long getSampledPacketErrorCount() {
- if (mBeginningSample != null && mEndingSample != null) {
- long rxPacketErrorCount = getSampledRxPacketErrorCount();
- long txPacketErrorCount = getSampledTxPacketErrorCount();
- return rxPacketErrorCount + txPacketErrorCount;
- } else {
- return LinkQualityInfo.UNKNOWN_LONG;
- }
- }
-
- public long getSampledRxPacketErrorCount() {
- synchronized(mSamplingDataLock) {
- if (mBeginningSample != null && mEndingSample != null) {
- return mEndingSample.mRxPacketErrorCount - mBeginningSample.mRxPacketErrorCount;
- } else {
- return LinkQualityInfo.UNKNOWN_LONG;
- }
- }
- }
-
- public long getSampleTimestamp() {
- synchronized(mSamplingDataLock) {
- if (mEndingSample != null) {
- return mEndingSample.mTimestamp;
- } else {
- return LinkQualityInfo.UNKNOWN_LONG;
- }
- }
- }
-
- public int getSampleDuration() {
- synchronized(mSamplingDataLock) {
- if (mBeginningSample != null && mEndingSample != null) {
- return (int) (mEndingSample.mTimestamp - mBeginningSample.mTimestamp);
- } else {
- return LinkQualityInfo.UNKNOWN_INT;
- }
- }
- }
-
- public void setCommonLinkQualityInfoFields(LinkQualityInfo li) {
- synchronized(mSamplingDataLock) {
- li.setLastDataSampleTime(getSampleTimestamp());
- li.setDataSampleDuration(getSampleDuration());
- li.setPacketCount(getSampledPacketCount());
- li.setPacketErrorCount(getSampledPacketErrorCount());
- }
- }
-}
-
diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java
index c848993..c26af06 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -19,6 +19,7 @@ package android.net;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
+import android.annotation.SystemApi;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.Service;
@@ -26,6 +27,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.net.Network;
import android.net.NetworkUtils;
import android.os.Binder;
import android.os.IBinder;
@@ -164,6 +166,32 @@ public class VpnService extends Service {
}
/**
+ * Version of {@link #prepare(Context)} which does not require user consent.
+ *
+ * <p>Requires {@link android.Manifest.permission#CONTROL_VPN} and should generally not be
+ * used. Only acceptable in situations where user consent has been obtained through other means.
+ *
+ * <p>Once this is run, future preparations may be done with the standard prepare method as this
+ * will authorize the package to prepare the VPN without consent in the future.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void prepareAndAuthorize(Context context) {
+ IConnectivityManager cm = getService();
+ String packageName = context.getPackageName();
+ try {
+ // Only prepare if we're not already prepared.
+ if (!cm.prepareVpn(packageName, null)) {
+ cm.prepareVpn(null, packageName);
+ }
+ cm.setVpnPackageAuthorization(true);
+ } catch (RemoteException e) {
+ // ignore
+ }
+ }
+
+ /**
* Protect a socket from VPN connections. After protecting, data sent
* through this socket will go directly to the underlying network,
* so its traffic will not be forwarded through the VPN.
@@ -261,6 +289,46 @@ public class VpnService extends Service {
}
/**
+ * Sets the underlying networks used by the VPN for its upstream connections.
+ *
+ * <p>Used by the system to know the actual networks that carry traffic for apps affected by
+ * this VPN in order to present this information to the user (e.g., via status bar icons).
+ *
+ * <p>This method only needs to be called if the VPN has explicitly bound its underlying
+ * communications channels &mdash; such as the socket(s) passed to {@link #protect(int)} &mdash;
+ * to a {@code Network} using APIs such as {@link Network#bindSocket(Socket)} or
+ * {@link Network#bindSocket(DatagramSocket)}. The VPN should call this method every time
+ * the set of {@code Network}s it is using changes.
+ *
+ * <p>{@code networks} is one of the following:
+ * <ul>
+ * <li><strong>a non-empty array</strong>: an array of one or more {@link Network}s, in
+ * decreasing preference order. For example, if this VPN uses both wifi and mobile (cellular)
+ * networks to carry app traffic, but prefers or uses wifi more than mobile, wifi should appear
+ * first in the array.</li>
+ * <li><strong>an empty array</strong>: a zero-element array, meaning that the VPN has no
+ * underlying network connection, and thus, app traffic will not be sent or received.</li>
+ * <li><strong>null</strong>: (default) signifies that the VPN uses whatever is the system's
+ * default network. I.e., it doesn't use the {@code bindSocket} or {@code bindDatagramSocket}
+ * APIs mentioned above to send traffic over specific channels.</li>
+ * </ul>
+ *
+ * <p>This call will succeed only if the VPN is currently established. For setting this value
+ * when the VPN has not yet been established, see {@link Builder#setUnderlyingNetworks}.
+ *
+ * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers.
+ *
+ * @return {@code true} on success.
+ */
+ public boolean setUnderlyingNetworks(Network[] networks) {
+ try {
+ return getService().setUnderlyingNetworksForVpn(networks);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
* Return the communication interface to the service. This method returns
* {@code null} on {@link Intent}s other than {@link #SERVICE_INTERFACE}
* action. Applications overriding this method must identify the intent
@@ -433,7 +501,7 @@ public class VpnService extends Service {
}
}
}
- mRoutes.add(new RouteInfo(new LinkAddress(address, prefixLength), null));
+ mRoutes.add(new RouteInfo(new IpPrefix(address, prefixLength), null));
mConfig.updateAllowedFamilies(address);
return this;
}
@@ -636,6 +704,20 @@ public class VpnService extends Service {
}
/**
+ * Sets the underlying networks used by the VPN for its upstream connections.
+ *
+ * @see VpnService#setUnderlyingNetworks
+ *
+ * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers.
+ *
+ * @return this {@link Builder} object to facilitate chaining method calls.
+ */
+ public Builder setUnderlyingNetworks(Network[] networks) {
+ mConfig.underlyingNetworks = networks != null ? networks.clone() : null;
+ return this;
+ }
+
+ /**
* Create a VPN interface using the parameters supplied to this
* builder. The interface works on IP packets, and a file descriptor
* is returned for the application to access them. Each read
diff --git a/core/java/android/net/http/AndroidHttpClient.java b/core/java/android/net/http/AndroidHttpClient.java
index 04f3974..a262076 100644
--- a/core/java/android/net/http/AndroidHttpClient.java
+++ b/core/java/android/net/http/AndroidHttpClient.java
@@ -74,7 +74,13 @@ import java.util.zip.GZIPOutputStream;
* To retain cookies, simply add a cookie store to the HttpContext:</p>
*
* <pre>context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);</pre>
+ *
+ * @deprecated Please use {@link java.net.URLConnection} and friends instead.
+ * The Apache HTTP client is no longer maintained and may be removed in a future
+ * release. Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
*/
+@Deprecated
public final class AndroidHttpClient implements HttpClient {
// Gzip of data shorter than this probably won't be worthwhile
@@ -108,7 +114,13 @@ public final class AndroidHttpClient implements HttpClient {
* @param userAgent to report in your HTTP requests
* @param context to use for caching SSL sessions (may be null for no caching)
* @return AndroidHttpClient for you to use for all your requests.
+ *
+ * @deprecated Please use {@link java.net.URLConnection} and friends instead. See
+ * {@link android.net.SSLCertificateSocketFactory} for SSL cache support. If you'd
+ * like to set a custom useragent, please use {@link java.net.URLConnection#setRequestProperty(String, String)}
+ * with {@code field} set to {@code User-Agent}.
*/
+ @Deprecated
public static AndroidHttpClient newInstance(String userAgent, Context context) {
HttpParams params = new BasicHttpParams();
@@ -148,7 +160,13 @@ public final class AndroidHttpClient implements HttpClient {
* Create a new HttpClient with reasonable defaults (which you can update).
* @param userAgent to report in your HTTP requests.
* @return AndroidHttpClient for you to use for all your requests.
+ *
+ * @deprecated Please use {@link java.net.URLConnection} and friends instead. See
+ * {@link android.net.SSLCertificateSocketFactory} for SSL cache support. If you'd
+ * like to set a custom useragent, please use {@link java.net.URLConnection#setRequestProperty(String, String)}
+ * with {@code field} set to {@code User-Agent}.
*/
+ @Deprecated
public static AndroidHttpClient newInstance(String userAgent) {
return newInstance(userAgent, null /* session cache */);
}
diff --git a/core/java/android/nfc/BeamShareData.java b/core/java/android/nfc/BeamShareData.java
index c30ba14..918ec3d 100644
--- a/core/java/android/nfc/BeamShareData.java
+++ b/core/java/android/nfc/BeamShareData.java
@@ -3,6 +3,7 @@ package android.nfc;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
/**
* Class to IPC data to be shared over Android Beam.
@@ -14,11 +15,13 @@ import android.os.Parcelable;
public final class BeamShareData implements Parcelable {
public final NdefMessage ndefMessage;
public final Uri[] uris;
+ public final UserHandle userHandle;
public final int flags;
- public BeamShareData(NdefMessage msg, Uri[] uris, int flags) {
+ public BeamShareData(NdefMessage msg, Uri[] uris, UserHandle userHandle, int flags) {
this.ndefMessage = msg;
this.uris = uris;
+ this.userHandle = userHandle;
this.flags = flags;
}
@@ -35,6 +38,7 @@ public final class BeamShareData implements Parcelable {
if (urisLength > 0) {
dest.writeTypedArray(uris, 0);
}
+ dest.writeParcelable(userHandle, 0);
dest.writeInt(this.flags);
}
@@ -49,9 +53,10 @@ public final class BeamShareData implements Parcelable {
uris = new Uri[numUris];
source.readTypedArray(uris, Uri.CREATOR);
}
+ UserHandle userHandle = source.readParcelable(UserHandle.class.getClassLoader());
int flags = source.readInt();
- return new BeamShareData(msg, uris, flags);
+ return new BeamShareData(msg, uris, userHandle, flags);
}
@Override
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 5b926ad..961a3f4 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -60,4 +60,6 @@ interface INfcAdapter
void addNfcUnlockHandler(INfcUnlockHandler unlockHandler, in int[] techList);
void removeNfcUnlockHandler(INfcUnlockHandler unlockHandler);
+
+ void verifyNfcPermission();
}
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index 8643f2e..d009295 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -18,12 +18,14 @@ package android.nfc;
import android.app.Activity;
import android.app.Application;
+import android.content.ContentProvider;
import android.content.Intent;
import android.net.Uri;
import android.nfc.NfcAdapter.ReaderCallback;
import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
import java.util.ArrayList;
@@ -252,7 +254,11 @@ public final class NfcActivityManager extends IAppCallback.Stub
isResumed = state.resumed;
}
if (isResumed) {
+ // requestNfcServiceCallback() verifies permission also
requestNfcServiceCallback();
+ } else {
+ // Crash API calls early in case NFC permission is missing
+ verifyNfcPermission();
}
}
@@ -266,7 +272,11 @@ public final class NfcActivityManager extends IAppCallback.Stub
isResumed = state.resumed;
}
if (isResumed) {
+ // requestNfcServiceCallback() verifies permission also
requestNfcServiceCallback();
+ } else {
+ // Crash API calls early in case NFC permission is missing
+ verifyNfcPermission();
}
}
@@ -279,7 +289,11 @@ public final class NfcActivityManager extends IAppCallback.Stub
isResumed = state.resumed;
}
if (isResumed) {
+ // requestNfcServiceCallback() verifies permission also
requestNfcServiceCallback();
+ } else {
+ // Crash API calls early in case NFC permission is missing
+ verifyNfcPermission();
}
}
@@ -293,7 +307,11 @@ public final class NfcActivityManager extends IAppCallback.Stub
isResumed = state.resumed;
}
if (isResumed) {
+ // requestNfcServiceCallback() verifies permission also
requestNfcServiceCallback();
+ } else {
+ // Crash API calls early in case NFC permission is missing
+ verifyNfcPermission();
}
}
@@ -306,7 +324,11 @@ public final class NfcActivityManager extends IAppCallback.Stub
isResumed = state.resumed;
}
if (isResumed) {
+ // requestNfcServiceCallback() verifies permission also
requestNfcServiceCallback();
+ } else {
+ // Crash API calls early in case NFC permission is missing
+ verifyNfcPermission();
}
}
@@ -322,6 +344,14 @@ public final class NfcActivityManager extends IAppCallback.Stub
}
}
+ void verifyNfcPermission() {
+ try {
+ NfcAdapter.sService.verifyNfcPermission();
+ } catch (RemoteException e) {
+ mAdapter.attemptDeadServiceRecovery(e);
+ }
+ }
+
/** Callback from NFC service, usually on binder thread */
@Override
public BeamShareData createBeamShareData() {
@@ -350,19 +380,24 @@ public final class NfcActivityManager extends IAppCallback.Stub
if (urisCallback != null) {
uris = urisCallback.createBeamUris(mDefaultEvent);
if (uris != null) {
+ ArrayList<Uri> validUris = new ArrayList<Uri>();
for (Uri uri : uris) {
if (uri == null) {
Log.e(TAG, "Uri not allowed to be null.");
- return null;
+ continue;
}
String scheme = uri.getScheme();
if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
!scheme.equalsIgnoreCase("content"))) {
Log.e(TAG, "Uri needs to have " +
"either scheme file or scheme content");
- return null;
+ continue;
}
+ uri = ContentProvider.maybeAddUserId(uri, UserHandle.myUserId());
+ validUris.add(uri);
}
+
+ uris = validUris.toArray(new Uri[validUris.size()]);
}
}
if (uris != null && uris.length > 0) {
@@ -372,7 +407,7 @@ public final class NfcActivityManager extends IAppCallback.Stub
Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
- return new BeamShareData(message, uris, flags);
+ return new BeamShareData(message, uris, UserHandle.CURRENT, flags);
}
/** Callback from NFC service, usually on binder thread */
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 4f91d19..7785f2b 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -209,9 +209,9 @@ public abstract class AsyncTask<Params, Progress, Result> {
private static final int MESSAGE_POST_RESULT = 0x1;
private static final int MESSAGE_POST_PROGRESS = 0x2;
- private static final InternalHandler sHandler = new InternalHandler();
-
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
+ private static InternalHandler sHandler;
+
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
@@ -265,9 +265,13 @@ public abstract class AsyncTask<Params, Progress, Result> {
FINISHED,
}
- /** @hide Used to force static handler to be created. */
- public static void init() {
- sHandler.getLooper();
+ private static Handler getHandler() {
+ synchronized (AsyncTask.class) {
+ if (sHandler == null) {
+ sHandler = new InternalHandler();
+ }
+ return sHandler;
+ }
}
/** @hide */
@@ -315,7 +319,7 @@ public abstract class AsyncTask<Params, Progress, Result> {
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
- Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
+ Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
@@ -620,7 +624,7 @@ public abstract class AsyncTask<Params, Progress, Result> {
*/
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
- sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
+ getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
@@ -635,10 +639,14 @@ public abstract class AsyncTask<Params, Progress, Result> {
}
private static class InternalHandler extends Handler {
+ public InternalHandler() {
+ super(Looper.getMainLooper());
+ }
+
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
- AsyncTaskResult result = (AsyncTaskResult) msg.obj;
+ AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 9589aac..1b02141 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -329,7 +329,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a Boolean, or null
*/
- void putBoolean(String key, boolean value) {
+ public void putBoolean(String key, boolean value) {
unparcel();
mMap.put(key, value);
}
@@ -497,7 +497,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a boolean array object, or null
*/
- void putBooleanArray(String key, boolean[] value) {
+ public void putBooleanArray(String key, boolean[] value) {
unparcel();
mMap.put(key, value);
}
@@ -617,7 +617,7 @@ public class BaseBundle {
* @param key a String
* @return a boolean value
*/
- boolean getBoolean(String key) {
+ public boolean getBoolean(String key) {
unparcel();
if (DEBUG) Log.d(TAG, "Getting boolean in "
+ Integer.toHexString(System.identityHashCode(this)));
@@ -654,7 +654,7 @@ public class BaseBundle {
* @param defaultValue Value to return if key does not exist
* @return a boolean value
*/
- boolean getBoolean(String key, boolean defaultValue) {
+ public boolean getBoolean(String key, boolean defaultValue) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1072,7 +1072,7 @@ public class BaseBundle {
* @param key a String, or null
* @return a boolean[] value, or null
*/
- boolean[] getBooleanArray(String key) {
+ public boolean[] getBooleanArray(String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 461469c..cd45cfb 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -91,11 +91,6 @@ public abstract class BatteryStats implements Parcelable {
public static final int WIFI_MULTICAST_ENABLED = 7;
/**
- * A constant indicating an audio turn on timer
- */
- public static final int AUDIO_TURNED_ON = 7;
-
- /**
* A constant indicating a video turn on timer
*/
public static final int VIDEO_TURNED_ON = 8;
@@ -131,6 +126,11 @@ public abstract class BatteryStats implements Parcelable {
public static final int JOB = 14;
/**
+ * A constant indicating an audio turn on timer
+ */
+ public static final int AUDIO_TURNED_ON = 15;
+
+ /**
* Include all of the data in the stats, including previously saved data.
*/
public static final int STATS_SINCE_CHARGED = 0;
@@ -459,6 +459,20 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getStarts(int which);
/**
+ * Returns the number of times the process has crashed.
+ *
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+ */
+ public abstract int getNumCrashes(int which);
+
+ /**
+ * Returns the number of times the process has ANRed.
+ *
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+ */
+ public abstract int getNumAnrs(int which);
+
+ /**
* Returns the cpu time spent in microseconds while the process was in the foreground.
* @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
* @return foreground cpu time in microseconds
@@ -590,6 +604,7 @@ public abstract class BatteryStats implements Parcelable {
public static final byte CMD_CURRENT_TIME = 5;
public static final byte CMD_OVERFLOW = 6;
public static final byte CMD_RESET = 7;
+ public static final byte CMD_SHUTDOWN = 8;
public byte cmd = CMD_NULL;
@@ -694,8 +709,10 @@ public abstract class BatteryStats implements Parcelable {
public static final int EVENT_USER_RUNNING = 0x0007;
// Events for foreground user.
public static final int EVENT_USER_FOREGROUND = 0x0008;
+ // Events for connectivity changed.
+ public static final int EVENT_CONNECTIVITY_CHANGED = 0x0009;
// Number of event types.
- public static final int EVENT_COUNT = 0x0009;
+ public static final int EVENT_COUNT = 0x000a;
// Mask to extract out only the type part of the event.
public static final int EVENT_TYPE_MASK = ~(EVENT_FLAG_START|EVENT_FLAG_FINISH);
@@ -1107,6 +1124,13 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getLowPowerModeEnabledCount(int which);
/**
+ * Returns the number of times that connectivity state changed.
+ *
+ * {@hide}
+ */
+ public abstract int getNumConnectivityChange(int which);
+
+ /**
* Returns the time in microseconds that the phone has been on while the device was
* running on battery.
*
@@ -1306,11 +1330,11 @@ public abstract class BatteryStats implements Parcelable {
};
public static final String[] HISTORY_EVENT_NAMES = new String[] {
- "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg"
+ "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn"
};
public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
- "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf"
+ "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn"
};
/**
@@ -1871,6 +1895,7 @@ 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 int connChanges = getNumConnectivityChange(which);
final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
final long wifiOnTime = getWifiOnTime(rawRealtime, which);
final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which);
@@ -1941,7 +1966,7 @@ public abstract class BatteryStats implements Parcelable {
fullWakeLockTimeTotal / 1000, partialWakeLockTimeTotal / 1000,
0 /*legacy input event count*/, getMobileRadioActiveTime(rawRealtime, which) / 1000,
getMobileRadioActiveAdjustedTime(which) / 1000, interactiveTime / 1000,
- lowPowerModeEnabledTime / 1000);
+ lowPowerModeEnabledTime / 1000, connChanges);
// Dump screen brightness stats
Object[] args = new Object[NUM_SCREEN_BRIGHTNESS_BINS];
@@ -2271,12 +2296,14 @@ public abstract class BatteryStats implements Parcelable {
final long userMillis = ps.getUserTime(which) * 10;
final long systemMillis = ps.getSystemTime(which) * 10;
final long foregroundMillis = ps.getForegroundTime(which) * 10;
- final long starts = ps.getStarts(which);
+ final int starts = ps.getStarts(which);
+ final int numCrashes = ps.getNumCrashes(which);
+ final int numAnrs = ps.getNumAnrs(which);
if (userMillis != 0 || systemMillis != 0 || foregroundMillis != 0
- || starts != 0) {
+ || starts != 0 || numAnrs != 0 || numCrashes != 0) {
dumpLine(pw, uid, category, PROCESS_DATA, ent.getKey(), userMillis,
- systemMillis, foregroundMillis, starts);
+ systemMillis, foregroundMillis, starts, numAnrs, numCrashes);
}
}
}
@@ -2457,6 +2484,11 @@ public abstract class BatteryStats implements Parcelable {
sb.append("("); sb.append(formatRatioLocked(phoneOnTime, whichBatteryRealtime));
sb.append(") "); sb.append(getPhoneOnCount(which));
}
+ int connChanges = getNumConnectivityChange(which);
+ if (connChanges != 0) {
+ pw.print(prefix);
+ pw.print(" Connectivity changes: "); pw.println(connChanges);
+ }
// Calculate wakelock times across all uids.
long fullWakeLockTimeTotalMicros = 0;
@@ -3273,11 +3305,13 @@ public abstract class BatteryStats implements Parcelable {
systemTime = ps.getSystemTime(which);
foregroundTime = ps.getForegroundTime(which);
starts = ps.getStarts(which);
+ final int numCrashes = ps.getNumCrashes(which);
+ final int numAnrs = ps.getNumAnrs(which);
numExcessive = which == STATS_SINCE_CHARGED
? ps.countExcessivePowers() : 0;
if (userTime != 0 || systemTime != 0 || foregroundTime != 0 || starts != 0
- || numExcessive != 0) {
+ || numExcessive != 0 || numCrashes != 0 || numAnrs != 0) {
sb.setLength(0);
sb.append(prefix); sb.append(" Proc ");
sb.append(ent.getKey()); sb.append(":\n");
@@ -3285,9 +3319,26 @@ public abstract class BatteryStats implements Parcelable {
formatTime(sb, userTime); sb.append("usr + ");
formatTime(sb, systemTime); sb.append("krn ; ");
formatTime(sb, foregroundTime); sb.append("fg");
- if (starts != 0) {
+ if (starts != 0 || numCrashes != 0 || numAnrs != 0) {
sb.append("\n"); sb.append(prefix); sb.append(" ");
- sb.append(starts); sb.append(" proc starts");
+ boolean hasOne = false;
+ if (starts != 0) {
+ hasOne = true;
+ sb.append(starts); sb.append(" starts");
+ }
+ if (numCrashes != 0) {
+ if (hasOne) {
+ sb.append(", ");
+ }
+ hasOne = true;
+ sb.append(numCrashes); sb.append(" crashes");
+ }
+ if (numAnrs != 0) {
+ if (hasOne) {
+ sb.append(", ");
+ }
+ sb.append(numAnrs); sb.append(" anrs");
+ }
}
pw.println(sb.toString());
for (int e=0; e<numExcessive; e++) {
@@ -3479,6 +3530,11 @@ public abstract class BatteryStats implements Parcelable {
pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
rec.currentTime).toString());
}
+ } else if (rec.cmd == HistoryItem.CMD_SHUTDOWN) {
+ if (checkin) {
+ pw.print(":");
+ }
+ pw.println("SHUTDOWN");
} else if (rec.cmd == HistoryItem.CMD_OVERFLOW) {
if (checkin) {
pw.print(":");
@@ -3669,6 +3725,45 @@ public abstract class BatteryStats implements Parcelable {
pw.print(suffix);
}
+ private static boolean dumpTimeEstimate(PrintWriter pw, String label, long[] steps,
+ int count, long modesOfInterest, long modeValues) {
+ if (count <= 0) {
+ return false;
+ }
+ long total = 0;
+ int numOfInterest = 0;
+ for (int i=0; i<count; i++) {
+ long initMode = (steps[i] & STEP_LEVEL_INITIAL_MODE_MASK)
+ >> STEP_LEVEL_INITIAL_MODE_SHIFT;
+ long modMode = (steps[i] & STEP_LEVEL_MODIFIED_MODE_MASK)
+ >> STEP_LEVEL_MODIFIED_MODE_SHIFT;
+ // If the modes of interest didn't change during this step period...
+ if ((modMode&modesOfInterest) == 0) {
+ // And the mode values during this period match those we are measuring...
+ if ((initMode&modesOfInterest) == modeValues) {
+ // Then this can be used to estimate the total time!
+ numOfInterest++;
+ total += steps[i] & STEP_LEVEL_TIME_MASK;
+ }
+ }
+ }
+ if (numOfInterest <= 0) {
+ return false;
+ }
+
+ // The estimated time is the average time we spend in each level, multipled
+ // by 100 -- the total number of battery levels
+ long estimatedTime = (total / numOfInterest) * 100;
+
+ pw.print(label);
+ StringBuilder sb = new StringBuilder(64);
+ formatTimeMs(sb, estimatedTime);
+ pw.print(sb);
+ pw.println();
+
+ return true;
+ }
+
private static boolean dumpDurationSteps(PrintWriter pw, String header, long[] steps,
int count, boolean checkin) {
if (count <= 0) {
@@ -3760,7 +3855,8 @@ public abstract class BatteryStats implements Parcelable {
if (histStart >= 0 && !printed) {
if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
|| rec.cmd == HistoryItem.CMD_RESET
- || rec.cmd == HistoryItem.CMD_START) {
+ || rec.cmd == HistoryItem.CMD_START
+ || rec.cmd == HistoryItem.CMD_SHUTDOWN) {
printed = true;
hprinter.printNextItem(pw, rec, baseTime, checkin,
(flags&DUMP_VERBOSE) != 0);
@@ -3923,6 +4019,38 @@ public abstract class BatteryStats implements Parcelable {
TimeUtils.formatDuration(timeRemaining / 1000, pw);
pw.println();
}
+ dumpTimeEstimate(pw, " Estimated screen off time: ",
+ getDischargeStepDurationsArray(), getNumDischargeStepDurations(),
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ (Display.STATE_OFF-1));
+ dumpTimeEstimate(pw, " Estimated screen off power save time: ",
+ getDischargeStepDurationsArray(), getNumDischargeStepDurations(),
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ (Display.STATE_OFF-1)|STEP_LEVEL_MODE_POWER_SAVE);
+ dumpTimeEstimate(pw, " Estimated screen on time: ",
+ getDischargeStepDurationsArray(), getNumDischargeStepDurations(),
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ (Display.STATE_ON-1));
+ dumpTimeEstimate(pw, " Estimated screen on power save time: ",
+ getDischargeStepDurationsArray(), getNumDischargeStepDurations(),
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ (Display.STATE_ON-1)|STEP_LEVEL_MODE_POWER_SAVE);
+ dumpTimeEstimate(pw, " Estimated screen doze time: ",
+ getDischargeStepDurationsArray(), getNumDischargeStepDurations(),
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ (Display.STATE_DOZE-1));
+ dumpTimeEstimate(pw, " Estimated screen doze power save time: ",
+ getDischargeStepDurationsArray(), getNumDischargeStepDurations(),
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ (Display.STATE_DOZE-1)|STEP_LEVEL_MODE_POWER_SAVE);
+ dumpTimeEstimate(pw, " Estimated screen doze suspend time: ",
+ getDischargeStepDurationsArray(), getNumDischargeStepDurations(),
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ (Display.STATE_DOZE_SUSPEND-1));
+ dumpTimeEstimate(pw, " Estimated screen doze suspend power save time: ",
+ getDischargeStepDurationsArray(), getNumDischargeStepDurations(),
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ (Display.STATE_DOZE_SUSPEND-1)|STEP_LEVEL_MODE_POWER_SAVE);
pw.println();
}
if (dumpDurationSteps(pw, "Charge step durations:", getChargeStepDurationsArray(),
@@ -3955,7 +4083,7 @@ public abstract class BatteryStats implements Parcelable {
prepareForDumpLocked();
dumpLine(pw, 0 /* uid */, "i" /* category */, VERSION_DATA,
- "11", getParcelVersion(), getStartPlatformVersion(), getEndPlatformVersion());
+ "12", getParcelVersion(), getStartPlatformVersion(), getEndPlatformVersion());
long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index d1ad313..b209690 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -20,8 +20,11 @@ import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.telephony.TelephonyProperties;
+
import dalvik.system.VMRuntime;
+import java.util.Objects;
+
/**
* Information about the current build, extracted from system properties.
*/
@@ -589,6 +592,11 @@ public class Build {
* </ul>
*/
public static final int LOLLIPOP = 21;
+
+ /**
+ * Lollipop with an extra sugar coating on the outside!
+ */
+ public static final int LOLLIPOP_MR1 = 22;
}
/** The type of build, like "user" or "eng". */
@@ -636,6 +644,32 @@ public class Build {
}
}
+ /**
+ * Check that device fingerprint is defined and that it matches across
+ * various partitions.
+ *
+ * @hide
+ */
+ public static boolean isFingerprintConsistent() {
+ final String system = SystemProperties.get("ro.build.fingerprint");
+ final String vendor = SystemProperties.get("ro.vendor.build.fingerprint");
+
+ if (TextUtils.isEmpty(system)) {
+ Slog.e(TAG, "Required ro.build.fingerprint is empty!");
+ return false;
+ }
+
+ if (!TextUtils.isEmpty(vendor)) {
+ if (!Objects.equals(system, vendor)) {
+ Slog.e(TAG, "Mismatched fingerprints; system reported " + system
+ + " but vendor reported " + vendor);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
// The following properties only make sense for internal engineering builds.
public static final long TIME = getLong("ro.build.date.utc") * 1000;
public static final String USER = getString("ro.build.user");
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index a9aa570..c5c5372 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -252,18 +252,6 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
}
/**
- * Inserts a Boolean value into the mapping of this Bundle, replacing
- * any existing value for the given key. Either key or value may be null.
- *
- * @param key a String, or null
- * @param value a Boolean, or null
- */
- @Override
- public void putBoolean(String key, boolean value) {
- super.putBoolean(key, value);
- }
-
- /**
* Inserts a byte value into the mapping of this Bundle, replacing
* any existing value for the given key.
*
@@ -460,18 +448,6 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
}
/**
- * Inserts a boolean array value into the mapping of this Bundle, replacing
- * any existing value for the given key. Either key or value may be null.
- *
- * @param key a String, or null
- * @param value a boolean array object, or null
- */
- @Override
- public void putBooleanArray(String key, boolean[] value) {
- super.putBooleanArray(key, value);
- }
-
- /**
* Inserts a byte array value into the mapping of this Bundle, replacing
* any existing value for the given key. Either key or value may be null.
*
@@ -579,31 +555,6 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
}
/**
- * Returns the value associated with the given key, or false if
- * no mapping of the desired type exists for the given key.
- *
- * @param key a String
- * @return a boolean value
- */
- @Override
- public boolean getBoolean(String key) {
- return super.getBoolean(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 boolean value
- */
- @Override
- public boolean getBoolean(String key, boolean defaultValue) {
- return super.getBoolean(key, defaultValue);
- }
-
- /**
* Returns the value associated with the given key, or (byte) 0 if
* no mapping of the desired type exists for the given key.
*
@@ -939,19 +890,6 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* value is explicitly associated with the key.
*
* @param key a String, or null
- * @return a boolean[] value, or null
- */
- @Override
- public boolean[] getBooleanArray(String key) {
- return super.getBooleanArray(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 byte[] value, or null
*/
@Override
diff --git a/core/java/android/os/ConditionVariable.java b/core/java/android/os/ConditionVariable.java
index 07b221c..1e820f9 100644
--- a/core/java/android/os/ConditionVariable.java
+++ b/core/java/android/os/ConditionVariable.java
@@ -109,7 +109,7 @@ public class ConditionVariable
* <p>
* If the condition is already opened, return immediately.
*
- * @param timeout the minimum time to wait in milliseconds.
+ * @param timeout the maximum time to wait in milliseconds.
*
* @return true if the condition was opened, false if the call returns
* because of the timeout.
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 9377def..2d92c7b 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -165,7 +165,7 @@ public final class Debug
public int otherSwappedOut;
/** @hide */
- public static final int NUM_OTHER_STATS = 16;
+ public static final int NUM_OTHER_STATS = 17;
/** @hide */
public static final int NUM_DVK_STATS = 8;
@@ -296,26 +296,27 @@ public final class Debug
case 1: return "Stack";
case 2: return "Cursor";
case 3: return "Ashmem";
- case 4: return "Other dev";
- case 5: return ".so mmap";
- case 6: return ".jar mmap";
- case 7: return ".apk mmap";
- case 8: return ".ttf mmap";
- case 9: return ".dex mmap";
- case 10: return "code mmap";
- case 11: return "image mmap";
- case 12: return "Other mmap";
- case 13: return "Graphics";
- case 14: return "GL";
- case 15: return "Memtrack";
- case 16: return ".Heap";
- case 17: return ".LOS";
- case 18: return ".LinearAlloc";
- case 19: return ".GC";
- case 20: return ".JITCache";
- case 21: return ".Zygote";
- case 22: return ".NonMoving";
- case 23: return ".IndirectRef";
+ case 4: return "Gfx dev";
+ case 5: return "Other dev";
+ case 6: return ".so mmap";
+ case 7: return ".jar mmap";
+ case 8: return ".apk mmap";
+ case 9: return ".ttf mmap";
+ case 10: return ".dex mmap";
+ case 11: return ".oat mmap";
+ case 12: return ".art mmap";
+ case 13: return "Other mmap";
+ case 14: return "EGL mtrack";
+ case 15: return "GL mtrack";
+ case 16: return "Other mtrack";
+ case 17: return ".Heap";
+ case 18: return ".LOS";
+ case 19: return ".LinearAlloc";
+ case 20: return ".GC";
+ case 21: return ".JITCache";
+ case 22: return ".Zygote";
+ case 23: return ".NonMoving";
+ case 24: return ".IndirectRef";
default: return "????";
}
}
@@ -1073,9 +1074,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Retrieves the PSS memory used by the process as given by the
* smaps. Optionally supply a long array of 1 entry to also
- * receive the uss of the process. @hide
+ * receive the uss of the process, and another array to also
+ * retrieve the separate memtrack size. @hide
*/
- public static native long getPss(int pid, long[] outUss);
+ public static native long getPss(int pid, long[] outUss, long[] outMemtrack);
/** @hide */
public static final int MEMINFO_TOTAL = 0;
@@ -1096,7 +1098,15 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/** @hide */
public static final int MEMINFO_ZRAM_TOTAL = 8;
/** @hide */
- public static final int MEMINFO_COUNT = 9;
+ public static final int MEMINFO_MAPPED = 9;
+ /** @hide */
+ public static final int MEMINFO_VM_ALLOC_USED = 10;
+ /** @hide */
+ public static final int MEMINFO_PAGE_TABLES = 11;
+ /** @hide */
+ public static final int MEMINFO_KERNEL_STACK = 12;
+ /** @hide */
+ public static final int MEMINFO_COUNT = 13;
/**
* Retrieves /proc/meminfo. outSizes is filled with fields
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index fe47f5b..0a724a1 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -17,9 +17,8 @@
package android.os;
import android.system.ErrnoException;
-import android.text.TextUtils;
import android.system.Os;
-import android.system.OsConstants;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
@@ -403,20 +402,89 @@ public class FileUtils {
return success;
}
+ private static boolean isValidExtFilenameChar(char c) {
+ switch (c) {
+ case '\0':
+ case '/':
+ return false;
+ default:
+ return true;
+ }
+ }
+
/**
- * Assert that given filename is valid on ext4.
+ * Check if given filename is valid for an ext4 filesystem.
*/
public static boolean isValidExtFilename(String name) {
+ return (name != null) && name.equals(buildValidExtFilename(name));
+ }
+
+ /**
+ * Mutate the given filename to make it valid for an ext4 filesystem,
+ * replacing any invalid characters with "_".
+ */
+ public static String buildValidExtFilename(String name) {
if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
- return false;
+ return "(invalid)";
}
+ final StringBuilder res = new StringBuilder(name.length());
for (int i = 0; i < name.length(); i++) {
final char c = name.charAt(i);
- if (c == '\0' || c == '/') {
+ if (isValidExtFilenameChar(c)) {
+ res.append(c);
+ } else {
+ res.append('_');
+ }
+ }
+ return res.toString();
+ }
+
+ private static boolean isValidFatFilenameChar(char c) {
+ if ((0x00 <= c && c <= 0x1f)) {
+ return false;
+ }
+ switch (c) {
+ case '"':
+ case '*':
+ case '/':
+ case ':':
+ case '<':
+ case '>':
+ case '?':
+ case '\\':
+ case '|':
+ case 0x7F:
return false;
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Check if given filename is valid for a FAT filesystem.
+ */
+ public static boolean isValidFatFilename(String name) {
+ return (name != null) && name.equals(buildValidFatFilename(name));
+ }
+
+ /**
+ * Mutate the given filename to make it valid for a FAT filesystem,
+ * replacing any invalid characters with "_".
+ */
+ public static String buildValidFatFilename(String name) {
+ if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
+ return "(invalid)";
+ }
+ final StringBuilder res = new StringBuilder(name.length());
+ for (int i = 0; i < name.length(); i++) {
+ final char c = name.charAt(i);
+ if (isValidFatFilenameChar(c)) {
+ res.append(c);
+ } else {
+ res.append('_');
}
}
- return true;
+ return res.toString();
}
public static String rewriteAfterRename(File beforeDir, File afterDir, String path) {
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 52db060..878b7a0 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -156,7 +156,7 @@ public class Handler {
* one that is strictly asynchronous.
*
* Asynchronous messages represent interrupts or events that do not require global ordering
- * with represent to synchronous messages. Asynchronous messages are not subject to
+ * with respect to synchronous messages. Asynchronous messages are not subject to
* the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
*
* @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
@@ -176,7 +176,7 @@ public class Handler {
* one that is strictly asynchronous.
*
* Asynchronous messages represent interrupts or events that do not require global ordering
- * with represent to synchronous messages. Asynchronous messages are not subject to
+ * with respect to synchronous messages. Asynchronous messages are not subject to
* the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
*
* @param callback The callback interface in which to handle messages, or null.
@@ -214,7 +214,7 @@ public class Handler {
* one that is strictly asynchronous.
*
* Asynchronous messages represent interrupts or events that do not require global ordering
- * with represent to synchronous messages. Asynchronous messages are not subject to
+ * with respect to synchronous messages. Asynchronous messages are not subject to
* the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
*
* @param looper The looper, must not be null.
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 07649e7..f0660eb 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -92,6 +92,11 @@ interface INetworkManagementService
void enableIpv6(String iface);
/**
+ * Enables or enables IPv6 ND offload.
+ */
+ void setInterfaceIpv6NdOffload(String iface, boolean enable);
+
+ /**
* Retrieves the network routes currently configured on the specified
* interface
*/
@@ -338,19 +343,19 @@ interface INetworkManagementService
void removeVpnUidRanges(int netId, in UidRange[] ranges);
/**
- * Start the clatd (464xlat) service
+ * Start the clatd (464xlat) service on the given interface.
*/
void startClatd(String interfaceName);
/**
- * Stop the clatd (464xlat) service
+ * Stop the clatd (464xlat) service on the given interface.
*/
- void stopClatd();
+ void stopClatd(String interfaceName);
/**
- * Determine whether the clatd (464xlat) service has been started
+ * Determine whether the clatd (464xlat) service has been started on the given interface.
*/
- boolean isClatdStarted();
+ boolean isClatdStarted(String interfaceName);
/**
* Start listening for mobile activity state changes.
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 182dbee..16dac7d 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -49,7 +49,7 @@ interface IPowerManager
void crash(String message);
void setStayOnSetting(int val);
- void setMaximumScreenOffTimeoutFromDeviceAdmin(int timeMs);
+ void boostScreenBrightness(long time);
// temporarily overrides the screen brightness settings to allow the user to
// see the effect of a settings change without applying it immediately
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index 6a0bddc..8c75847 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -417,38 +417,42 @@ public final class Message implements Parcelable {
}
/**
- * Returns true if the message is asynchronous.
- *
- * Asynchronous messages represent interrupts or events that do not require global ordering
- * with represent to synchronous messages. Asynchronous messages are not subject to
- * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
+ * Returns true if the message is asynchronous, meaning that it is not
+ * subject to {@link Looper} synchronization barriers.
*
* @return True if the message is asynchronous.
*
* @see #setAsynchronous(boolean)
- * @see MessageQueue#enqueueSyncBarrier(long)
- * @see MessageQueue#removeSyncBarrier(int)
- *
- * @hide
*/
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
/**
- * Sets whether the message is asynchronous.
- *
- * Asynchronous messages represent interrupts or events that do not require global ordering
- * with represent to synchronous messages. Asynchronous messages are not subject to
- * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
+ * Sets whether the message is asynchronous, meaning that it is not
+ * subject to {@link Looper} synchronization barriers.
+ * <p>
+ * Certain operations, such as view invalidation, may introduce synchronization
+ * barriers into the {@link Looper}'s message queue to prevent subsequent messages
+ * from being delivered until some condition is met. In the case of view invalidation,
+ * messages which are posted after a call to {@link android.view.View#invalidate}
+ * are suspended by means of a synchronization barrier until the next frame is
+ * ready to be drawn. The synchronization barrier ensures that the invalidation
+ * request is completely handled before resuming.
+ * </p><p>
+ * Asynchronous messages are exempt from synchronization barriers. They typically
+ * represent interrupts, input events, and other signals that must be handled independently
+ * even while other work has been suspended.
+ * </p><p>
+ * Note that asynchronous messages may be delivered out of order with respect to
+ * synchronous messages although they are always delivered in order among themselves.
+ * If the relative order of these messages matters then they probably should not be
+ * asynchronous in the first place. Use with caution.
+ * </p>
*
* @param async True if the message is asynchronous.
*
* @see #isAsynchronous()
- * @see MessageQueue#enqueueSyncBarrier(long)
- * @see MessageQueue#removeSyncBarrier(int)
- *
- * @hide
*/
public void setAsynchronous(boolean async) {
if (async) {
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 5230128..3d5215b 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -276,7 +276,7 @@ public final class Parcel {
private static native byte[] nativeMarshall(long nativePtr);
private static native void nativeUnmarshall(
- long nativePtr, byte[] data, int offest, int length);
+ long nativePtr, byte[] data, int offset, int length);
private static native void nativeAppendFrom(
long thisNativePtr, long otherNativePtr, int offset, int length);
private static native boolean nativeHasFileDescriptors(long nativePtr);
@@ -340,6 +340,12 @@ public final class Parcel {
}
}
+ /** @hide */
+ public static native long getGlobalAllocSize();
+
+ /** @hide */
+ public static native long getGlobalAllocCount();
+
/**
* Returns the total amount of data contained in the parcel.
*/
@@ -432,8 +438,8 @@ public final class Parcel {
/**
* Set the bytes in data to be the raw bytes of this Parcel.
*/
- public final void unmarshall(byte[] data, int offest, int length) {
- nativeUnmarshall(mNativePtr, data, offest, length);
+ public final void unmarshall(byte[] data, int offset, int length) {
+ nativeUnmarshall(mNativePtr, data, offset, length);
}
public final void appendFrom(Parcel parcel, int offset, int length) {
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index c01f688..3a44428 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -96,7 +96,8 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa
!(value instanceof Double) && !(value instanceof String) &&
!(value instanceof int[]) && !(value instanceof long[]) &&
!(value instanceof double[]) && !(value instanceof String[]) &&
- !(value instanceof PersistableBundle) && (value != null)) {
+ !(value instanceof PersistableBundle) && (value != null) &&
+ !(value instanceof Boolean) && !(value instanceof boolean[])) {
throw new IllegalArgumentException("Bad value in PersistableBundle key=" + key +
" value=" + value);
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 3b6ce53..d52dd30 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -669,6 +669,28 @@ public final class PowerManager {
}
/**
+ * Boosts the brightness of the screen to maximum for a predetermined
+ * period of time. This is used to make the screen more readable in bright
+ * daylight for a short duration.
+ * <p>
+ * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+ * </p>
+ *
+ * @param time The time when the request to boost was issued, in the
+ * {@link SystemClock#uptimeMillis()} time base. This timestamp is used to correctly
+ * order the boost request with other power management functions. It should be set
+ * to the timestamp of the input event that caused the request to boost.
+ *
+ * @hide Requires signature permission.
+ */
+ public void boostScreenBrightness(long time) {
+ try {
+ mService.boostScreenBrightness(time);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Sets the brightness of the backlights (screen, keyboard, button).
* <p>
* Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
@@ -813,6 +835,21 @@ public final class PowerManager {
}
/**
+ * Turn off the device.
+ *
+ * @param confirm If true, shows a shutdown confirmation dialog.
+ * @param wait If true, this call waits for the shutdown to complete and does not return.
+ *
+ * @hide
+ */
+ public void shutdown(boolean confirm, boolean wait) {
+ try {
+ mService.shutdown(confirm, wait);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Intent that is broadcast when the state of {@link #isPowerSaveMode()} changes.
* This broadcast is only sent to registered receivers.
*/
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 14f4a83..6f31768 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -25,6 +25,58 @@ import android.view.Display;
*/
public abstract class PowerManagerInternal {
/**
+ * Wakefulness: The device is asleep. It can only be awoken by a call to wakeUp().
+ * The screen should be off or in the process of being turned off by the display controller.
+ * The device typically passes through the dozing state first.
+ */
+ public static final int WAKEFULNESS_ASLEEP = 0;
+
+ /**
+ * Wakefulness: The device is fully awake. It can be put to sleep by a call to goToSleep().
+ * When the user activity timeout expires, the device may start dreaming or go to sleep.
+ */
+ public static final int WAKEFULNESS_AWAKE = 1;
+
+ /**
+ * Wakefulness: The device is dreaming. It can be awoken by a call to wakeUp(),
+ * which ends the dream. The device goes to sleep when goToSleep() is called, when
+ * the dream ends or when unplugged.
+ * User activity may brighten the screen but does not end the dream.
+ */
+ public static final int WAKEFULNESS_DREAMING = 2;
+
+ /**
+ * Wakefulness: The device is dozing. It is almost asleep but is allowing a special
+ * low-power "doze" dream to run which keeps the display on but lets the application
+ * processor be suspended. It can be awoken by a call to wakeUp() which ends the dream.
+ * The device fully goes to sleep if the dream cannot be started or ends on its own.
+ */
+ public static final int WAKEFULNESS_DOZING = 3;
+
+ public static String wakefulnessToString(int wakefulness) {
+ switch (wakefulness) {
+ case WAKEFULNESS_ASLEEP:
+ return "Asleep";
+ case WAKEFULNESS_AWAKE:
+ return "Awake";
+ case WAKEFULNESS_DREAMING:
+ return "Dreaming";
+ case WAKEFULNESS_DOZING:
+ return "Dozing";
+ default:
+ return Integer.toString(wakefulness);
+ }
+ }
+
+ /**
+ * Returns true if the wakefulness state represents an interactive state
+ * as defined by {@link android.os.PowerManager#isInteractive}.
+ */
+ public static boolean isInteractive(int wakefulness) {
+ return wakefulness == WAKEFULNESS_AWAKE || wakefulness == WAKEFULNESS_DREAMING;
+ }
+
+ /**
* Used by the window manager to override the screen brightness based on the
* current foreground activity.
*
@@ -56,6 +108,13 @@ public abstract class PowerManagerInternal {
public abstract void setUserActivityTimeoutOverrideFromWindowManager(long timeoutMillis);
/**
+ * Used by device administration to set the maximum screen off timeout.
+ *
+ * This method must only be called by the device administration policy manager.
+ */
+ public abstract void setMaximumScreenOffTimeoutFromDeviceAdmin(int timeMs);
+
+ /**
* Used by the dream manager to override certain properties while dozing.
*
* @param screenState The overridden screen state, or {@link Display.STATE_UNKNOWN}
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 0ff5f6a..74e064e 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -45,12 +45,12 @@ public final class UserHandle implements Parcelable {
/** @hide A user id to indicate that we would like to send to the current
* user, but if this is calling from a user process then we will send it
- * to the caller's user instead of failing wiht a security exception */
+ * to the caller's user instead of failing with a security exception */
public static final int USER_CURRENT_OR_SELF = -3;
/** @hide A user handle to indicate that we would like to send to the current
* user, but if this is calling from a user process then we will send it
- * to the caller's user instead of failing wiht a security exception */
+ * to the caller's user instead of failing with a security exception */
public static final UserHandle CURRENT_OR_SELF = new UserHandle(USER_CURRENT_OR_SELF);
/** @hide An undefined user id */
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 3234e77..d124a49 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -56,6 +56,7 @@ public class UserManager {
/**
* Specifies if a user is disallowed from changing Wi-Fi
* access points. The default value is <code>false</code>.
+ * <p/>This restriction has no effect in a managed profile.
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
@@ -87,8 +88,10 @@ public class UserManager {
public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
/**
- * Specifies if a user is disallowed from toggling location sharing.
+ * Specifies if a user is disallowed from turning on location sharing.
* The default value is <code>false</code>.
+ * <p/>In a managed profile, location sharing always reflects the primary user's setting, but
+ * can be overridden and forced off by setting this restriction to true in the managed profile.
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
@@ -112,6 +115,7 @@ public class UserManager {
/**
* Specifies if a user is disallowed from configuring bluetooth.
* The default value is <code>false</code>.
+ * <p/>This restriction has no effect in a managed profile.
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
@@ -144,8 +148,10 @@ public class UserManager {
public static final String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
/**
- * Specifies if a user is disallowed from removing itself and other
- * users. The default value is <code>false</code>.
+ * When set on the primary user this specifies if the user can remove other users.
+ * When set on a secondary user, this specifies if the user can remove itself.
+ * This restriction has no effect on managed profiles.
+ * The default value is <code>false</code>.
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
@@ -168,6 +174,7 @@ public class UserManager {
/**
* Specifies if a user is disallowed from configuring VPN.
* The default value is <code>false</code>.
+ * This restriction has no effect in a managed profile.
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
@@ -192,6 +199,8 @@ public class UserManager {
* Specifies if a user is disallowed from factory resetting
* from Settings. This can only be set by device owners and profile owners on the primary user.
* The default value is <code>false</code>.
+ * <p/>This restriction has no effect on secondary users and managed profiles since only the
+ * primary user can factory reset the device.
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
@@ -204,6 +213,8 @@ public class UserManager {
* Specifies if a user is disallowed from adding new users and
* profiles. This can only be set by device owners and profile owners on the primary user.
* The default value is <code>false</code>.
+ * <p/>This restriction has no effect on secondary users and managed profiles since only the
+ * primary user can add other users.
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
@@ -227,6 +238,8 @@ public class UserManager {
* Specifies if a user is disallowed from configuring cell
* broadcasts. This can only be set by device owners and profile owners on the primary user.
* The default value is <code>false</code>.
+ * <p/>This restriction has no effect on secondary users and managed profiles since only the
+ * primary user can configure cell broadcasts.
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
@@ -239,6 +252,8 @@ public class UserManager {
* Specifies if a user is disallowed from configuring mobile
* networks. This can only be set by device owners and profile owners on the primary user.
* The default value is <code>false</code>.
+ * <p/>This restriction has no effect on secondary users and managed profiles since only the
+ * primary user can configure mobile networks.
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
@@ -307,6 +322,8 @@ public class UserManager {
* Specifies that the user is not allowed to make outgoing
* phone calls. Emergency calls are still permitted.
* The default value is <code>false</code>.
+ * <p/>This restriction has no effect on managed profiles since call intents are normally
+ * forwarded to the primary user.
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
@@ -367,10 +384,29 @@ public class UserManager {
* <p/>Type: Boolean
* @see #setUserRestrictions(Bundle)
* @see #getUserRestrictions()
- * @hide
*/
public static final String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";
+ /**
+ * Application restriction key that is used to indicate the pending arrival
+ * of real restrictions for the app.
+ *
+ * <p>
+ * Applications that support restrictions should check for the presence of this key.
+ * A <code>true</code> value indicates that restrictions may be applied in the near
+ * future but are not available yet. It is the responsibility of any
+ * management application that sets this flag to update it when the final
+ * restrictions are enforced.
+ *
+ * <p/>Key for application restrictions.
+ * <p/>Type: Boolean
+ * @see android.app.admin.DevicePolicyManager#setApplicationRestrictions(
+ * android.content.ComponentName, String, Bundle)
+ * @see android.app.admin.DevicePolicyManager#getApplicationRestrictions(
+ * android.content.ComponentName, String)
+ */
+ public static final String KEY_RESTRICTIONS_PENDING = "restrictions_pending";
+
/** @hide */
public static final int PIN_VERIFICATION_FAILED_INCORRECT = -3;
/** @hide */
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index cf407f4..116110e 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -856,6 +856,38 @@ public interface IMountService extends IInterface {
}
return _result;
}
+
+ @Override
+ public long lastMaintenance() throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ long _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ mRemote.transact(Stub.TRANSACTION_lastMaintenance, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readLong();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ @Override
+ public void runMaintenance() throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ mRemote.transact(Stub.TRANSACTION_runMaintenance, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return;
+ }
}
private static final String DESCRIPTOR = "IMountService";
@@ -942,6 +974,10 @@ public interface IMountService extends IInterface {
static final int TRANSACTION_resizeSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 40;
+ static final int TRANSACTION_lastMaintenance = IBinder.FIRST_CALL_TRANSACTION + 41;
+
+ static final int TRANSACTION_runMaintenance = IBinder.FIRST_CALL_TRANSACTION + 42;
+
/**
* Cast an IBinder object into an IMountService interface, generating a
* proxy if needed.
@@ -1347,6 +1383,19 @@ public interface IMountService extends IInterface {
reply.writeInt(resultCode);
return true;
}
+ case TRANSACTION_lastMaintenance: {
+ data.enforceInterface(DESCRIPTOR);
+ long lastMaintenance = lastMaintenance();
+ reply.writeNoException();
+ reply.writeLong(lastMaintenance);
+ return true;
+ }
+ case TRANSACTION_runMaintenance: {
+ data.enforceInterface(DESCRIPTOR);
+ runMaintenance();
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
}
@@ -1617,4 +1666,18 @@ public interface IMountService extends IInterface {
public String getField(String field) throws RemoteException;
public int resizeSecureContainer(String id, int sizeMb, String key) throws RemoteException;
+
+ /**
+ * Report the time of the last maintenance operation such as fstrim.
+ * @return Timestamp of the last maintenance operation, in the
+ * System.currentTimeMillis() time base
+ * @throws RemoteException
+ */
+ public long lastMaintenance() throws RemoteException;
+
+ /**
+ * Kick off an immediate maintenance operation
+ * @throws RemoteException
+ */
+ public void runMaintenance() throws RemoteException;
}
diff --git a/core/java/android/preference/ListPreference.java b/core/java/android/preference/ListPreference.java
index 8081a54..9482a72 100644
--- a/core/java/android/preference/ListPreference.java
+++ b/core/java/android/preference/ListPreference.java
@@ -162,10 +162,10 @@ public class ListPreference extends DialogPreference {
@Override
public CharSequence getSummary() {
final CharSequence entry = getEntry();
- if (mSummary == null || entry == null) {
+ if (mSummary == null) {
return super.getSummary();
} else {
- return String.format(mSummary, entry);
+ return String.format(mSummary, entry == null ? "" : entry);
}
}
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 56d5617..0224c73 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -215,7 +215,7 @@ public class Preference implements Comparable<Preference> {
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.Preference, defStyleAttr, defStyleRes);
- for (int i = a.getIndexCount(); i >= 0; i--) {
+ for (int i = a.getIndexCount() - 1; i >= 0; i--) {
int attr = a.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.Preference_icon:
diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java
index ad940c6..0a0e625 100644
--- a/core/java/android/preference/PreferenceManager.java
+++ b/core/java/android/preference/PreferenceManager.java
@@ -156,7 +156,7 @@ public class PreferenceManager {
* should be used ANY time a preference will be displayed, since some preference
* types need an Activity for managed queries.
*/
- private PreferenceManager(Context context) {
+ /*package*/ PreferenceManager(Context context) {
init(context);
}
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 671f722..c3dd4ce 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -44,24 +44,30 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
public interface Callback {
void onSampleStarting(SeekBarVolumizer sbv);
+ void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch);
+ void onMuted(boolean muted);
}
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 boolean mAffectedByRingerMode;
+ private boolean mNotificationOrRing;
private final Receiver mReceiver = new Receiver();
- private final Observer mVolumeObserver;
+ private Handler mHandler;
+ private Observer mVolumeObserver;
private int mOriginalStreamVolume;
private Ringtone mRingtone;
private int mLastProgress = -1;
+ private boolean mMuted;
private SeekBar mSeekBar;
private int mVolumeBeforeMute = -1;
+ private int mRingerMode;
private static final int MSG_SET_STREAM_VOLUME = 0;
private static final int MSG_START_SAMPLE = 1;
@@ -69,22 +75,22 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
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) {
+ public SeekBarVolumizer(Context context, int streamType, Uri defaultUri, Callback callback) {
mContext = context;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mStreamType = streamType;
+ mAffectedByRingerMode = mAudioManager.isStreamAffectedByRingerMode(mStreamType);
+ mNotificationOrRing = isNotificationOrRing(mStreamType);
+ if (mNotificationOrRing) {
+ mRingerMode = mAudioManager.getRingerModeInternal();
+ }
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);
+ mMuted = mAudioManager.isStreamMute(mStreamType);
+ if (mCallback != null) {
+ mCallback.onMuted(mMuted);
+ }
if (defaultUri == null) {
if (mStreamType == AudioManager.STREAM_RING) {
defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
@@ -95,7 +101,10 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
}
}
mDefaultUri = defaultUri;
- mHandler.sendEmptyMessage(MSG_INIT_SAMPLE);
+ }
+
+ private static boolean isNotificationOrRing(int stream) {
+ return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
}
public void setSeekBar(SeekBar seekBar) {
@@ -105,10 +114,23 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
mSeekBar = seekBar;
mSeekBar.setOnSeekBarChangeListener(null);
mSeekBar.setMax(mMaxStreamVolume);
- mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume);
+ updateSeekBar();
mSeekBar.setOnSeekBarChangeListener(this);
}
+ protected void updateSeekBar() {
+ if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
+ mSeekBar.setEnabled(true);
+ mSeekBar.setProgress(0);
+ } else if (mMuted) {
+ mSeekBar.setEnabled(false);
+ mSeekBar.setProgress(0);
+ } else {
+ mSeekBar.setEnabled(true);
+ mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume);
+ }
+ }
+
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
@@ -139,6 +161,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
}
private void postStartSample() {
+ if (mHandler == null) return;
mHandler.removeMessages(MSG_START_SAMPLE);
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
@@ -159,7 +182,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
}
}
- void postStopSample() {
+ private void postStopSample() {
+ if (mHandler == null) return;
// remove pending delayed start messages
mHandler.removeMessages(MSG_START_SAMPLE);
mHandler.removeMessages(MSG_STOP_SAMPLE);
@@ -173,27 +197,44 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
}
public void stop() {
+ if (mHandler == null) return; // already stopped
postStopSample();
mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
- mSeekBar.setOnSeekBarChangeListener(null);
mReceiver.setListening(false);
+ mSeekBar.setOnSeekBarChangeListener(null);
mHandler.getLooper().quitSafely();
+ mHandler = null;
+ mVolumeObserver = null;
+ }
+
+ public void start() {
+ if (mHandler != null) return; // already started
+ HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
+ thread.start();
+ mHandler = new Handler(thread.getLooper(), this);
+ mHandler.sendEmptyMessage(MSG_INIT_SAMPLE);
+ mVolumeObserver = new Observer(mHandler);
+ mContext.getContentResolver().registerContentObserver(
+ System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
+ false, mVolumeObserver);
+ mReceiver.setListening(true);
}
public void revertVolume() {
mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
}
- public void onProgressChanged(SeekBar seekBar, int progress,
- boolean fromTouch) {
- if (!fromTouch) {
- return;
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
+ if (fromTouch) {
+ postSetVolume(progress);
+ }
+ if (mCallback != null) {
+ mCallback.onProgressChanged(seekBar, progress, fromTouch);
}
-
- postSetVolume(progress);
}
- void postSetVolume(int progress) {
+ private void postSetVolume(int progress) {
+ if (mHandler == null) return;
// Do the volume changing separately to give responsive UI
mLastProgress = progress;
mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
@@ -266,14 +307,29 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
public void handleMessage(Message msg) {
if (msg.what == UPDATE_SLIDER) {
if (mSeekBar != null) {
- mSeekBar.setProgress(msg.arg1);
- mLastProgress = mSeekBar.getProgress();
+ mLastProgress = msg.arg1;
+ final boolean muted = msg.arg2 != 0;
+ if (muted != mMuted) {
+ mMuted = muted;
+ if (mCallback != null) {
+ mCallback.onMuted(mMuted);
+ }
+ }
+ updateSeekBar();
}
}
}
- public void postUpdateSlider(int volume) {
- obtainMessage(UPDATE_SLIDER, volume, 0).sendToTarget();
+ public void postUpdateSlider(int volume, boolean mute) {
+ obtainMessage(UPDATE_SLIDER, volume, mute ? 1 : 0).sendToTarget();
+ }
+ }
+
+ private void updateSlider() {
+ if (mSeekBar != null && mAudioManager != null) {
+ final int volume = mAudioManager.getStreamVolume(mStreamType);
+ final boolean mute = mAudioManager.isStreamMute(mStreamType);
+ mUiHandler.postUpdateSlider(volume, mute);
}
}
@@ -285,10 +341,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
- if (mSeekBar != null && mAudioManager != null) {
- final int volume = mAudioManager.getStreamVolume(mStreamType);
- mUiHandler.postUpdateSlider(volume);
- }
+ updateSlider();
}
}
@@ -300,6 +353,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
mListening = listening;
if (listening) {
final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
+ filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
mContext.registerReceiver(this, filter);
} else {
mContext.unregisterReceiver(this);
@@ -308,11 +362,23 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
@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);
+ final String action = intent.getAction();
+ if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
+ int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
+ final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType)
+ : (streamType == mStreamType);
+ if (mSeekBar != null && streamMatch && streamValue != -1) {
+ final boolean muted = mAudioManager.isStreamMute(mStreamType);
+ mUiHandler.postUpdateSlider(streamValue, muted);
+ }
+ } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
+ if (mNotificationOrRing) {
+ mRingerMode = mAudioManager.getRingerModeInternal();
+ }
+ if (mAffectedByRingerMode) {
+ updateSlider();
+ }
}
}
}
diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java
index df9e10e..a2da01b 100644
--- a/core/java/android/preference/VolumePreference.java
+++ b/core/java/android/preference/VolumePreference.java
@@ -67,6 +67,7 @@ public class VolumePreference extends SeekBarDialogPreference implements
final SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
mSeekBarVolumizer = new SeekBarVolumizer(getContext(), mStreamType, null, this);
+ mSeekBarVolumizer.start();
mSeekBarVolumizer.setSeekBar(seekBar);
getPreferenceManager().registerOnActivityStopListener(this);
@@ -116,7 +117,7 @@ public class VolumePreference extends SeekBarDialogPreference implements
public void onActivityStop() {
if (mSeekBarVolumizer != null) {
- mSeekBarVolumizer.postStopSample();
+ mSeekBarVolumizer.stopSample();
}
}
@@ -149,6 +150,16 @@ public class VolumePreference extends SeekBarDialogPreference implements
}
@Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
+ // noop
+ }
+
+ @Override
+ public void onMuted(boolean muted) {
+ // noop
+ }
+
+ @Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
if (isPersistent()) {
diff --git a/core/java/android/print/IPrintDocumentAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl
index 9d384fb..8f33e0b 100644
--- a/core/java/android/print/IPrintDocumentAdapter.aidl
+++ b/core/java/android/print/IPrintDocumentAdapter.aidl
@@ -37,4 +37,5 @@ oneway interface IPrintDocumentAdapter {
void write(in PageRange[] pages, in ParcelFileDescriptor fd,
IWriteResultCallback callback, int sequence);
void finish();
+ void kill(String reason);
}
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index bf8ac65..3fb812e 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -634,6 +634,17 @@ public final class PrintManager {
}
@Override
+ public void kill(String reason) {
+ synchronized (mLock) {
+ // If destroyed the handler is null.
+ if (!isDestroyedLocked()) {
+ mHandler.obtainMessage(MyHandler.MSG_ON_KILL,
+ reason).sendToTarget();
+ }
+ }
+ }
+
+ @Override
public void onActivityPaused(Activity activity) {
/* do nothing */
}
@@ -719,6 +730,7 @@ public final class PrintManager {
public static final int MSG_ON_LAYOUT = 2;
public static final int MSG_ON_WRITE = 3;
public static final int MSG_ON_FINISH = 4;
+ public static final int MSG_ON_KILL = 5;
public MyHandler(Looper looper) {
super(looper, null, true);
@@ -794,6 +806,15 @@ public final class PrintManager {
}
} break;
+ case MSG_ON_KILL: {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onKill()");
+ }
+
+ String reason = (String) message.obj;
+ throw new RuntimeException(reason);
+ }
+
default: {
throw new IllegalArgumentException("Unknown message: "
+ message.what);
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index c8d0fd5..f023df7 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -391,6 +391,7 @@ public class CallLog {
start, duration, dataUsage, false);
}
+
/**
* Adds a call to the call log.
*
@@ -463,6 +464,7 @@ public class CallLog {
values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString);
values.put(PHONE_ACCOUNT_ID, accountId);
values.put(NEW, Integer.valueOf(1));
+
if (callType == MISSED_TYPE) {
values.put(IS_READ, Integer.valueOf(0));
}
@@ -592,7 +594,7 @@ public class CallLog {
resolver.update(feedbackUri, new ContentValues(), null, null);
}
- /**
+ /*
* Update the normalized phone number for the given dataId in the ContactsProvider, based
* on the user's current country.
*/
@@ -601,18 +603,15 @@ public class CallLog {
if (TextUtils.isEmpty(number) || TextUtils.isEmpty(dataId)) {
return;
}
-
final String countryIso = getCurrentCountryIso(context);
if (TextUtils.isEmpty(countryIso)) {
return;
}
-
final String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number,
getCurrentCountryIso(context));
if (TextUtils.isEmpty(normalizedNumber)) {
return;
}
-
final ContentValues values = new ContentValues();
values.put(Phone.NORMALIZED_NUMBER, normalizedNumber);
resolver.update(Data.CONTENT_URI, values, Data._ID + "=?", new String[] {dataId});
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 932e873..1316471 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -637,7 +637,7 @@ public abstract class DocumentsProvider extends ContentProvider {
final Bundle out = new Bundle();
try {
if (METHOD_CREATE_DOCUMENT.equals(method)) {
- enforceWritePermissionInner(documentUri);
+ enforceWritePermissionInner(documentUri, null);
final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
@@ -651,7 +651,7 @@ public abstract class DocumentsProvider extends ContentProvider {
out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
} else if (METHOD_RENAME_DOCUMENT.equals(method)) {
- enforceWritePermissionInner(documentUri);
+ enforceWritePermissionInner(documentUri, null);
final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
final String newDocumentId = renameDocument(documentId, displayName);
@@ -675,7 +675,7 @@ public abstract class DocumentsProvider extends ContentProvider {
}
} else if (METHOD_DELETE_DOCUMENT.equals(method)) {
- enforceWritePermissionInner(documentUri);
+ enforceWritePermissionInner(documentUri, null);
deleteDocument(documentId);
// Document no longer exists, clean up any grants
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d08758b..ef0f72f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -787,11 +787,10 @@ public final class Settings {
* <p>
* Output: Nothing.
* @see android.service.notification.NotificationListenerService
- * @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_NOTIFICATION_LISTENER_SETTINGS
- = "android.settings.NOTIFICATION_LISTENER_SETTINGS";
+ = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
/**
* @hide
@@ -873,8 +872,9 @@ public final class Settings {
/**
* Activity Action: Show battery saver settings.
- *
- * @hide
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard
+ * against this.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_BATTERY_SAVER_SETTINGS
@@ -984,8 +984,8 @@ public final class Settings {
* InputDeviceIdentifier. This field is used by some activities to jump straight into the
* settings for the given device.
* <p>
- * Example: The {@link #INPUT_METHOD_SETTINGS} intent opens the keyboard layout dialog for the
- * given device.
+ * Example: The {@link #ACTION_INPUT_METHOD_SETTINGS} intent opens the keyboard layout
+ * dialog for the given device.
* @hide
*/
public static final String EXTRA_INPUT_DEVICE_IDENTIFIER = "input_device_identifier";
@@ -2625,12 +2625,6 @@ public final class Settings {
public static final String LOCK_TO_APP_ENABLED = "lock_to_app_enabled";
/**
- * Whether lock-to-app will lock the keyguard when exiting.
- * @hide
- */
- public static final String LOCK_TO_APP_EXIT_LOCKED = "lock_to_app_exit_locked";
-
- /**
* I am the lolrus.
* <p>
* Nonzero values indicate that the user has a bukkit.
@@ -2704,6 +2698,7 @@ public final class Settings {
POINTER_SPEED,
VIBRATE_WHEN_RINGING,
RINGTONE,
+ LOCK_TO_APP_ENABLED,
NOTIFICATION_SOUND
};
@@ -3017,7 +3012,6 @@ public final class Settings {
MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES);
MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_POLL_INTERVAL);
- MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_REPORT_XT_OVER_DEV);
MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_SAMPLE_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE);
MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_BUCKET_DURATION);
@@ -3667,6 +3661,12 @@ public final class Settings {
"lock_biometric_weak_flags";
/**
+ * Whether lock-to-app will lock the keyguard when exiting.
+ * @hide
+ */
+ public static final String LOCK_TO_APP_EXIT_LOCKED = "lock_to_app_exit_locked";
+
+ /**
* Whether autolock is enabled (0 = false, 1 = true)
*/
public static final String LOCK_PATTERN_ENABLED = "lock_pattern_autolock";
@@ -4349,6 +4349,16 @@ public final class Settings {
Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS;
/**
+ * The number of milliseconds to hold on to a PendingIntent based request. This delay gives
+ * the receivers of the PendingIntent an opportunity to make a new network request before
+ * the Network satisfying the request is potentially removed.
+ *
+ * @hide
+ */
+ public static final String CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS =
+ "connectivity_release_pending_intent_delay_ms";
+
+ /**
* Whether background data usage is allowed.
*
* @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH},
@@ -4588,13 +4598,6 @@ public final class Settings {
public static final String ANR_SHOW_BACKGROUND = "anr_show_background";
/**
- * (Experimental). If nonzero, WebView uses data reduction proxy to save network
- * bandwidth. Otherwise, WebView does not use data reduction proxy.
- * @hide
- */
- public static final String WEBVIEW_DATA_REDUCTION_PROXY = "webview_data_reduction_proxy";
-
- /**
* The {@link ComponentName} string of the service to be used as the voice recognition
* service.
*
@@ -4739,8 +4742,8 @@ public final class Settings {
public static final String SMS_DEFAULT_APPLICATION = "sms_default_application";
/**
- * Name of a package that the current user has explicitly allowed to see all of that
- * user's notifications.
+ * Names of the packages that the current user has explicitly allowed to
+ * see all of the user's notifications, separated by ':'.
*
* @hide
*/
@@ -4823,7 +4826,7 @@ public final class Settings {
* The timeout in milliseconds before the device fully goes to sleep after
* a period of inactivity. This value sets an upper bound on how long the device
* will stay awake or dreaming without user activity. It should generally
- * be longer than {@link #SCREEN_OFF_TIMEOUT} as otherwise the device
+ * be longer than {@link Settings.System#SCREEN_OFF_TIMEOUT} as otherwise the device
* will sleep before it ever has a chance to dream.
* <p>
* Use -1 to disable this timeout.
@@ -5013,10 +5016,19 @@ public final class Settings {
default:
throw new IllegalArgumentException("Invalid location mode: " + mode);
}
- boolean gpsSuccess = Settings.Secure.setLocationProviderEnabledForUser(
- cr, LocationManager.GPS_PROVIDER, gps, userId);
+ // Note it's important that we set the NLP mode first. The Google implementation
+ // of NLP clears its NLP consent setting any time it receives a
+ // LocationManager.PROVIDERS_CHANGED_ACTION broadcast and NLP is disabled. Also,
+ // it shows an NLP consent dialog any time it receives the broadcast, NLP is
+ // enabled, and the NLP consent is not set. If 1) we were to enable GPS first,
+ // 2) a setup wizard has its own NLP consent UI that sets the NLP consent setting,
+ // and 3) the receiver happened to complete before we enabled NLP, then the Google
+ // NLP would detect the attempt to enable NLP and show a redundant NLP consent
+ // dialog. Then the people who wrote the setup wizard would be sad.
boolean nlpSuccess = Settings.Secure.setLocationProviderEnabledForUser(
cr, LocationManager.NETWORK_PROVIDER, network, userId);
+ boolean gpsSuccess = Settings.Secure.setLocationProviderEnabledForUser(
+ cr, LocationManager.GPS_PROVIDER, gps, userId);
return gpsSuccess && nlpSuccess;
}
}
@@ -5086,6 +5098,13 @@ public final class Settings {
public static final String AIRPLANE_MODE_ON = "airplane_mode_on";
/**
+ * Whether Theater Mode is on.
+ * {@hide}
+ */
+ @SystemApi
+ public static final String THEATER_MODE_ON = "theater_mode_on";
+
+ /**
* Constant for use in AIRPLANE_MODE_RADIOS to specify Bluetooth radio.
*/
public static final String RADIO_BLUETOOTH = "bluetooth";
@@ -5447,8 +5466,6 @@ public final class Settings {
public static final String NETSTATS_GLOBAL_ALERT_BYTES = "netstats_global_alert_bytes";
/** {@hide} */
public static final String NETSTATS_SAMPLE_ENABLED = "netstats_sample_enabled";
- /** {@hide} */
- public static final String NETSTATS_REPORT_XT_OVER_DEV = "netstats_report_xt_over_dev";
/** {@hide} */
public static final String NETSTATS_DEV_BUCKET_DURATION = "netstats_dev_bucket_duration";
@@ -5544,6 +5561,13 @@ public final class Settings {
public static final String PACKAGE_VERIFIER_INCLUDE_ADB = "verifier_verify_adb_installs";
/**
+ * Time since last fstrim (milliseconds) after which we force one to happen
+ * during device startup. If unset, the default is 3 days.
+ * @hide
+ */
+ public static final String FSTRIM_MANDATORY_INTERVAL = "fstrim_mandatory_interval";
+
+ /**
* The interval in milliseconds at which to check packet counts on the
* mobile data interface when screen is on, to detect possible data
* connection problems.
@@ -5901,6 +5925,18 @@ public final class Settings {
public static final String WIFI_REENABLE_DELAY_MS = "wifi_reenable_delay";
/**
+ * Timeout for ephemeral networks when all known BSSIDs go out of range. We will disconnect
+ * from an ephemeral network if there is no BSSID for that network with a non-null score that
+ * has been seen in this time period.
+ *
+ * If this is less than or equal to zero, we use a more conservative behavior and only check
+ * for a non-null score from the currently connected or target BSSID.
+ * @hide
+ */
+ public static final String WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS =
+ "wifi_ephemeral_out_of_range_timeout_ms";
+
+ /**
* The number of milliseconds to delay when checking for data stalls during
* non-aggressive detection. (screen is turned off.)
* @hide
@@ -6105,7 +6141,7 @@ public final class Settings {
/**
* The number of milliseconds to delay before sending out
- * {@link ConnectivityManager#CONNECTIVITY_ACTION} broadcasts.
+ * {@link ConnectivityManager#CONNECTIVITY_ACTION} broadcasts. Ignored.
*
* @hide
*/
@@ -6350,6 +6386,14 @@ public final class Settings {
"preferred_network_mode";
/**
+ * Setting to 1 will hide carrier network settings.
+ * Default is 0.
+ * @hide
+ */
+ public static final String HIDE_CARRIER_NETWORK_SETTINGS =
+ "hide_carrier_network_settings";
+
+ /**
* Name of an application package to be debugged.
*/
public static final String DEBUG_APP = "debug_app";
@@ -6572,6 +6616,31 @@ public final class Settings {
public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt";
/**
+ * Whether the Volte/VT is enabled
+ * <p>
+ * Type: int (0 for false, 1 for true)
+ * @hide
+ */
+ public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+
+ /**
+ * Global override to disable VoLTE (independent of user setting)
+ * <p>
+ * Type: int (1 for disable VoLTE, 0 to use user configuration)
+ * @hide
+ */
+ public static final String VOLTE_FEATURE_DISABLED = "volte_feature_disabled";
+
+ /**
+ * Whether user can enable/disable LTE as a preferred network. A carrier might control
+ * this via gservices, OMA-DM, carrier app, etc.
+ * <p>
+ * Type: int (0 for false, 1 for true)
+ * @hide
+ */
+ public static final String LTE_SERVICE_FORCED = "lte_service_forced";
+
+ /**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
*
@@ -6602,7 +6671,8 @@ public final class Settings {
WIFI_NUM_OPEN_NETWORKS_KEPT,
EMERGENCY_TONE,
CALL_AUTO_RETRY,
- DOCK_AUDIO_MEDIA_ENABLED
+ DOCK_AUDIO_MEDIA_ENABLED,
+ LOW_POWER_MODE_TRIGGER_LEVEL
};
// Populated lazily, guarded by class object:
diff --git a/core/java/android/service/carrier/CarrierMessagingService.java b/core/java/android/service/carrier/CarrierMessagingService.java
new file mode 100644
index 0000000..3d6ebca
--- /dev/null
+++ b/core/java/android/service/carrier/CarrierMessagingService.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.carrier;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A service that receives calls from the system when new SMS and MMS are
+ * sent or received.
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * the {@link android.Manifest.permission#BIND_CARRIER_MESSAGING_SERVICE} permission
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
+ * <pre>
+ * &lt;service android:name=".MyMessagingService"
+ * android:label="&#64;string/service_name"
+ * android:permission="android.permission.BIND_CARRIER_MESSAGING_SERVICE">
+ * &lt;intent-filter>
+ * &lt;action android:name="android.service.carrier.CarrierMessagingService" />
+ * &lt;/intent-filter>
+ * &lt;/service></pre>
+ */
+public abstract class CarrierMessagingService extends Service {
+ /**
+ * The {@link android.content.Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE
+ = "android.service.carrier.CarrierMessagingService";
+
+ /**
+ * Indicates that an SMS or MMS message was successfully sent.
+ */
+ public static final int SEND_STATUS_OK = 0;
+
+ /**
+ * SMS/MMS sending failed. We should retry via the carrier network.
+ */
+ public static final int SEND_STATUS_RETRY_ON_CARRIER_NETWORK = 1;
+
+ /**
+ * SMS/MMS sending failed. We should not retry via the carrier network.
+ */
+ public static final int SEND_STATUS_ERROR = 2;
+
+ /**
+ * Successfully downloaded an MMS message.
+ */
+ public static final int DOWNLOAD_STATUS_OK = 0;
+
+ /**
+ * MMS downloading failed. We should retry via the carrier network.
+ */
+ public static final int DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK = 1;
+
+ /**
+ * MMS downloading failed. We should not retry via the carrier network.
+ */
+ public static final int DOWNLOAD_STATUS_ERROR = 2;
+
+ private final ICarrierMessagingWrapper mWrapper = new ICarrierMessagingWrapper();
+
+ /**
+ * Override this method to filter inbound SMS messages.
+ *
+ * @param pdu the PDUs of the message
+ * @param format the format of the PDUs, typically "3gpp" or "3gpp2"
+ * @param destPort the destination port of a binary SMS, this will be -1 for text SMS
+ * @param subId SMS subscription ID of the SIM
+ * @param callback result callback. Call with {@code true} to keep an inbound SMS message and
+ * deliver to SMS apps, and {@code false} to drop the message.
+ */
+ public void onFilterSms(@NonNull MessagePdu pdu, @NonNull String format, int destPort,
+ int subId, @NonNull ResultCallback<Boolean> callback) {
+ // optional
+ try {
+ callback.onReceiveResult(true);
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
+ * Override this method to intercept text SMSs sent from the device.
+ *
+ * @param text the text to send
+ * @param subId SMS subscription ID of the SIM
+ * @param destAddress phone number of the recipient of the message
+ * @param callback result callback. Call with a {@link SendSmsResult}.
+ */
+ public void onSendTextSms(
+ @NonNull String text, int subId, @NonNull String destAddress,
+ @NonNull ResultCallback<SendSmsResult> callback) {
+ // optional
+ try {
+ callback.onReceiveResult(new SendSmsResult(SEND_STATUS_RETRY_ON_CARRIER_NETWORK, 0));
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
+ * Override this method to intercept binary SMSs sent from the device.
+ *
+ * @param data the binary content
+ * @param subId SMS subscription ID of the SIM
+ * @param destAddress phone number of the recipient of the message
+ * @param destPort the destination port
+ * @param callback result callback. Call with a {@link SendSmsResult}.
+ */
+ public void onSendDataSms(@NonNull byte[] data, int subId,
+ @NonNull String destAddress, int destPort,
+ @NonNull ResultCallback<SendSmsResult> callback) {
+ // optional
+ try {
+ callback.onReceiveResult(new SendSmsResult(SEND_STATUS_RETRY_ON_CARRIER_NETWORK, 0));
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
+ * Override this method to intercept long SMSs sent from the device.
+ *
+ * @param parts a {@link List} of the message parts
+ * @param subId SMS subscription ID of the SIM
+ * @param destAddress phone number of the recipient of the message
+ * @param callback result callback. Call with a {@link SendMultipartSmsResult}.
+ */
+ public void onSendMultipartTextSms(@NonNull List<String> parts,
+ int subId, @NonNull String destAddress,
+ @NonNull ResultCallback<SendMultipartSmsResult> callback) {
+ // optional
+ try {
+ callback.onReceiveResult(
+ new SendMultipartSmsResult(SEND_STATUS_RETRY_ON_CARRIER_NETWORK, null));
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
+ * Override this method to intercept MMSs sent from the device.
+ *
+ * @param pduUri the content provider URI of the PDU to send
+ * @param subId SMS subscription ID of the SIM
+ * @param location the optional URI to send this MMS PDU. If this is {code null},
+ * the PDU should be sent to the default MMSC URL.
+ * @param callback result callback. Call with a {@link SendMmsResult}.
+ */
+ public void onSendMms(@NonNull Uri pduUri, int subId,
+ @Nullable Uri location, @NonNull ResultCallback<SendMmsResult> callback) {
+ // optional
+ try {
+ callback.onReceiveResult(new SendMmsResult(SEND_STATUS_RETRY_ON_CARRIER_NETWORK, null));
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
+ * Override this method to download MMSs received.
+ *
+ * @param contentUri the content provider URI of the PDU to be downloaded.
+ * @param subId SMS subscription ID of the SIM
+ * @param location the URI of the message to be downloaded.
+ * @param callback result callback. Call with a status code which is one of
+ * {@link #DOWNLOAD_STATUS_OK},
+ * {@link #DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK}, or {@link #DOWNLOAD_STATUS_ERROR}.
+ */
+ public void onDownloadMms(@NonNull Uri contentUri, int subId, @NonNull Uri location,
+ @NonNull ResultCallback<Integer> callback) {
+ // optional
+ try {
+ callback.onReceiveResult(DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK);
+ } catch (RemoteException ex) {
+ }
+ }
+
+ @Override
+ public @Nullable IBinder onBind(@NonNull Intent intent) {
+ if (!SERVICE_INTERFACE.equals(intent.getAction())) {
+ return null;
+ }
+ return mWrapper;
+ }
+
+ /**
+ * The result of sending an MMS.
+ */
+ public static final class SendMmsResult {
+ private int mSendStatus;
+ private byte[] mSendConfPdu;
+
+ /**
+ * Constructs a SendMmsResult with the MMS send result, and the SendConf PDU.
+ *
+ * @param sendStatus send status, one of {@link #SEND_STATUS_OK},
+ * {@link #SEND_STATUS_RETRY_ON_CARRIER_NETWORK}, and
+ * {@link #SEND_STATUS_ERROR}
+ * @param sendConfPdu a possibly {code null} SendConf PDU, which confirms that the message
+ * was sent. sendConfPdu is ignored if the {@code result} is not
+ * {@link #SEND_STATUS_OK}.
+ */
+ public SendMmsResult(int sendStatus, @Nullable byte[] sendConfPdu) {
+ mSendStatus = sendStatus;
+ mSendConfPdu = sendConfPdu;
+ }
+
+ /**
+ * Returns the send status of the just-sent MMS.
+ *
+ * @return the send status which is one of {@link #SEND_STATUS_OK},
+ * {@link #SEND_STATUS_RETRY_ON_CARRIER_NETWORK}, and {@link #SEND_STATUS_ERROR}
+ */
+ public int getSendStatus() {
+ return mSendStatus;
+ }
+
+ /**
+ * Returns the SendConf PDU, which confirms that the message was sent.
+ *
+ * @return the SendConf PDU
+ */
+ public @Nullable byte[] getSendConfPdu() {
+ return mSendConfPdu;
+ }
+ }
+
+ /**
+ * The result of sending an SMS.
+ */
+ public static final class SendSmsResult {
+ private final int mSendStatus;
+ private final int mMessageRef;
+
+ /**
+ * Constructs a SendSmsResult with the send status and message reference for the
+ * just-sent SMS.
+ *
+ * @param sendStatus send status, one of {@link #SEND_STATUS_OK},
+ * {@link #SEND_STATUS_RETRY_ON_CARRIER_NETWORK}, and {@link #SEND_STATUS_ERROR}.
+ * @param messageRef message reference of the just-sent SMS. This field is applicable only
+ * if send status is {@link #SEND_STATUS_OK}.
+ */
+ public SendSmsResult(int sendStatus, int messageRef) {
+ mSendStatus = sendStatus;
+ mMessageRef = messageRef;
+ }
+
+ /**
+ * Returns the message reference of the just-sent SMS.
+ *
+ * @return the message reference
+ */
+ public int getMessageRef() {
+ return mMessageRef;
+ }
+
+ /**
+ * Returns the send status of the just-sent SMS.
+ *
+ * @return the send status
+ */
+ public int getSendStatus() {
+ return mSendStatus;
+ }
+ }
+
+ /**
+ * The result of sending a multipart SMS.
+ */
+ public static final class SendMultipartSmsResult {
+ private final int mSendStatus;
+ private final int[] mMessageRefs;
+
+ /**
+ * Constructs a SendMultipartSmsResult with the send status and message references for the
+ * just-sent multipart SMS.
+ *
+ * @param sendStatus send status, one of {@link #SEND_STATUS_OK},
+ * {@link #SEND_STATUS_RETRY_ON_CARRIER_NETWORK}, and {@link #SEND_STATUS_ERROR}.
+ * @param messageRefs an array of message references, one for each part of the
+ * multipart SMS. This field is applicable only if send status is
+ * {@link #SEND_STATUS_OK}.
+ */
+ public SendMultipartSmsResult(int sendStatus, @Nullable int[] messageRefs) {
+ mSendStatus = sendStatus;
+ mMessageRefs = messageRefs;
+ }
+
+ /**
+ * Returns the message references of the just-sent multipart SMS.
+ *
+ * @return the message references, one for each part of the multipart SMS
+ */
+ public @Nullable int[] getMessageRefs() {
+ return mMessageRefs;
+ }
+
+ /**
+ * Returns the send status of the just-sent SMS.
+ *
+ * @return the send status
+ */
+ public int getSendStatus() {
+ return mSendStatus;
+ }
+ }
+
+ /**
+ * A callback interface used to provide results asynchronously.
+ */
+ public interface ResultCallback<T> {
+ /**
+ * Invoked when the result is available.
+ *
+ * @param result the result
+ */
+ public void onReceiveResult(@NonNull T result) throws RemoteException;
+ };
+
+ /**
+ * A wrapper around ICarrierMessagingService to enable the carrier messaging app to implement
+ * methods it cares about in the {@link ICarrierMessagingService} interface.
+ */
+ private class ICarrierMessagingWrapper extends ICarrierMessagingService.Stub {
+ @Override
+ public void filterSms(MessagePdu pdu, String format, int destPort,
+ int subId, final ICarrierMessagingCallback callback) {
+ onFilterSms(pdu, format, destPort, subId, new ResultCallback<Boolean>() {
+ @Override
+ public void onReceiveResult(final Boolean result) throws RemoteException {
+ callback.onFilterComplete(result);
+ }
+ });
+ }
+
+ @Override
+ public void sendTextSms(String text, int subId, String destAddress,
+ final ICarrierMessagingCallback callback) {
+ onSendTextSms(text, subId, destAddress, new ResultCallback<SendSmsResult>() {
+ @Override
+ public void onReceiveResult(final SendSmsResult result) throws RemoteException {
+ callback.onSendSmsComplete(result.getSendStatus(), result.getMessageRef());
+ }
+ });
+ }
+
+ @Override
+ public void sendDataSms(byte[] data, int subId, String destAddress, int destPort,
+ final ICarrierMessagingCallback callback) {
+ onSendDataSms(data, subId, destAddress, destPort, new ResultCallback<SendSmsResult>() {
+ @Override
+ public void onReceiveResult(final SendSmsResult result) throws RemoteException {
+ callback.onSendSmsComplete(result.getSendStatus(), result.getMessageRef());
+ }
+ });
+ }
+
+ @Override
+ public void sendMultipartTextSms(List<String> parts, int subId, String destAddress,
+ final ICarrierMessagingCallback callback) {
+ onSendMultipartTextSms(parts, subId, destAddress,
+ new ResultCallback<SendMultipartSmsResult>() {
+ @Override
+ public void onReceiveResult(final SendMultipartSmsResult result)
+ throws RemoteException {
+ callback.onSendMultipartSmsComplete(
+ result.getSendStatus(), result.getMessageRefs());
+ }
+ });
+ }
+
+ @Override
+ public void sendMms(Uri pduUri, int subId, Uri location,
+ final ICarrierMessagingCallback callback) {
+ onSendMms(pduUri, subId, location, new ResultCallback<SendMmsResult>() {
+ @Override
+ public void onReceiveResult(final SendMmsResult result) throws RemoteException {
+ callback.onSendMmsComplete(result.getSendStatus(), result.getSendConfPdu());
+ }
+ });
+ }
+
+ @Override
+ public void downloadMms(Uri pduUri, int subId, Uri location,
+ final ICarrierMessagingCallback callback) {
+ onDownloadMms(pduUri, subId, location, new ResultCallback<Integer>() {
+ @Override
+ public void onReceiveResult(Integer result) throws RemoteException {
+ callback.onDownloadMmsComplete(result);
+ }
+ });
+ }
+ }
+}
diff --git a/core/java/android/service/carrier/ICarrierMessagingCallback.aidl b/core/java/android/service/carrier/ICarrierMessagingCallback.aidl
new file mode 100644
index 0000000..6118a20
--- /dev/null
+++ b/core/java/android/service/carrier/ICarrierMessagingCallback.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.service.carrier;
+
+/**
+ * Callback interface definition for the Carrier Messaging Service client to get informed of the
+ * result of various API invocations.
+ * @hide
+ */
+oneway interface ICarrierMessagingCallback {
+ void onFilterComplete(boolean keepMessage);
+ void onSendSmsComplete(int result, int messageRef);
+ void onSendMultipartSmsComplete(int result, in int[] messageRefs);
+ void onSendMmsComplete(int result, in byte[] sendConfPdu);
+ void onDownloadMmsComplete(int result);
+}
diff --git a/core/java/android/service/carrier/ICarrierMessagingService.aidl b/core/java/android/service/carrier/ICarrierMessagingService.aidl
new file mode 100644
index 0000000..40a9047
--- /dev/null
+++ b/core/java/android/service/carrier/ICarrierMessagingService.aidl
@@ -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.service.carrier;
+
+import android.net.Uri;
+import android.service.carrier.ICarrierMessagingCallback;
+import android.service.carrier.MessagePdu;
+
+/**
+ * <p class="note"><strong>Note:</strong>
+ * This service can only be implemented by a carrier privileged app.
+ * @hide
+ */
+oneway interface ICarrierMessagingService {
+ /**
+ * Request filtering an incoming SMS message.
+ * The service will call callback.onFilterComplete with the filtering result.
+ *
+ * @param pdu the PDUs of the message
+ * @param format the format of the PDUs, typically "3gpp" or "3gpp2"
+ * @param destPort the destination port of a data SMS. It will be -1 for text SMS
+ * @param subId SMS subscription ID of the SIM
+ * @param callback the callback to notify upon completion
+ */
+ void filterSms(
+ in MessagePdu pdu, String format, int destPort, int subId,
+ in ICarrierMessagingCallback callback);
+
+ /**
+ * Request sending a new text SMS from the device.
+ * The service will call {@link ICarrierMessagingCallback#onSendSmsComplete} with the send
+ * status.
+ *
+ * @param text the text to send
+ * @param subId SMS subscription ID of the SIM
+ * @param destAddress phone number of the recipient of the message
+ * @param callback the callback to notify upon completion
+ */
+ void sendTextSms(String text, int subId, String destAddress,
+ in ICarrierMessagingCallback callback);
+
+ /**
+ * Request sending a new data SMS from the device.
+ * The service will call {@link ICarrierMessagingCallback#onSendSmsComplete} with the send
+ * status.
+ *
+ * @param data the data to send
+ * @param subId SMS subscription ID of the SIM
+ * @param destAddress phone number of the recipient of the message
+ * @param destPort port number of the recipient of the message
+ * @param callback the callback to notify upon completion
+ */
+ void sendDataSms(in byte[] data, int subId, String destAddress, int destPort,
+ in ICarrierMessagingCallback callback);
+
+ /**
+ * Request sending a new multi-part text SMS from the device.
+ * The service will call {@link ICarrierMessagingCallback#onSendMultipartSmsComplete}
+ * with the send status.
+ *
+ * @param parts the parts of the multi-part text SMS to send
+ * @param subId SMS subscription ID of the SIM
+ * @param destAddress phone number of the recipient of the message
+ * @param callback the callback to notify upon completion
+ */
+ void sendMultipartTextSms(in List<String> parts, int subId, String destAddress,
+ in ICarrierMessagingCallback callback);
+
+ /**
+ * Request sending a new MMS PDU from the device.
+ * The service will call {@link ICarrierMessagingCallback#onSendMmsComplete} with the send
+ * status.
+ *
+ * @param pduUri the content provider URI of the PDU to send
+ * @param subId SMS subscription ID of the SIM
+ * @param location the optional URI to send this MMS PDU. If this is {code null},
+ * the PDU should be sent to the default MMSC URL.
+ * @param callback the callback to notify upon completion
+ */
+ void sendMms(in Uri pduUri, int subId, in Uri location,
+ in ICarrierMessagingCallback callback);
+
+ /**
+ * Request downloading a new MMS.
+ * The service will call {@link ICarrierMessagingCallback#onDownloadMmsComplete} with the
+ * download status.
+ *
+ * @param pduUri the content provider URI of the PDU to be downloaded.
+ * @param subId SMS subscription ID of the SIM
+ * @param location the URI of the message to be downloaded.
+ * @param callback the callback to notify upon completion
+ */
+ void downloadMms(in Uri pduUri, int subId, in Uri location,
+ in ICarrierMessagingCallback callback);
+}
+
diff --git a/core/java/android/service/carrier/MessagePdu.aidl b/core/java/android/service/carrier/MessagePdu.aidl
new file mode 100644
index 0000000..f0528a5
--- /dev/null
+++ b/core/java/android/service/carrier/MessagePdu.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.carrier;
+
+parcelable MessagePdu;
diff --git a/core/java/android/service/carrier/MessagePdu.java b/core/java/android/service/carrier/MessagePdu.java
new file mode 100644
index 0000000..ca18e53
--- /dev/null
+++ b/core/java/android/service/carrier/MessagePdu.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.carrier;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A parcelable list of PDUs representing contents of a possibly multi-part SMS.
+ */
+public final class MessagePdu implements Parcelable {
+ private static final int NULL_LENGTH = -1;
+
+ private final List<byte[]> mPduList;
+
+ /**
+ * Constructs a MessagePdu with the list of message PDUs.
+ *
+ * @param pduList the list of message PDUs
+ */
+ public MessagePdu(@NonNull List<byte[]> pduList) {
+ if (pduList == null || pduList.contains(null)) {
+ throw new IllegalArgumentException("pduList must not be null or contain nulls");
+ }
+ mPduList = pduList;
+ }
+
+ /**
+ * Returns the contents of a possibly multi-part SMS.
+ *
+ * @return the list of PDUs
+ */
+ public @NonNull List<byte[]> getPdus() {
+ return mPduList;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mPduList == null) {
+ dest.writeInt(NULL_LENGTH);
+ } else {
+ dest.writeInt(mPduList.size());
+ for (byte[] messagePdu : mPduList) {
+ dest.writeByteArray(messagePdu);
+ }
+ }
+ }
+
+ /**
+ * Constructs a {@link MessagePdu} from a {@link Parcel}.
+ */
+ public static final Parcelable.Creator<MessagePdu> CREATOR
+ = new Parcelable.Creator<MessagePdu>() {
+ @Override
+ public MessagePdu createFromParcel(Parcel source) {
+ int size = source.readInt();
+ List<byte[]> pduList;
+ if (size == NULL_LENGTH) {
+ pduList = null;
+ } else {
+ pduList = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ pduList.add(source.createByteArray());
+ }
+ }
+ return new MessagePdu(pduList);
+ }
+
+ @Override
+ public MessagePdu[] newArray(int size) {
+ return new MessagePdu[size];
+ }
+ };
+}
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 3a91d1a..80bdbf1 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -160,7 +160,7 @@ public class Condition implements Parcelable {
}
public static boolean isValidId(Uri id, String pkg) {
- return id != null && id.getScheme().equals(SCHEME) && id.getAuthority().equals(pkg);
+ return id != null && SCHEME.equals(id.getScheme()) && pkg.equals(id.getAuthority());
}
public static final Parcelable.Creator<Condition> CREATOR
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 882a3c8..979a01b 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -17,11 +17,13 @@
package android.service.notification;
import android.content.ComponentName;
+import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.text.format.DateFormat;
import android.util.Slog;
import org.xmlpull.v1.XmlPullParser;
@@ -32,8 +34,11 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
+import java.util.Locale;
import java.util.Objects;
+import com.android.internal.R;
+
/**
* Persisted configuration for zen mode.
*
@@ -59,7 +64,7 @@ public class ZenModeConfig implements Parcelable {
public static final int[] MINUTE_BUCKETS = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 };
private static final int SECONDS_MS = 1000;
private static final int MINUTES_MS = 60 * SECONDS_MS;
- private static final int ZERO_VALUE_MS = 20 * SECONDS_MS;
+ private static final int ZERO_VALUE_MS = 10 * SECONDS_MS;
private static final boolean DEFAULT_ALLOW_EVENTS = true;
@@ -73,6 +78,7 @@ public class ZenModeConfig implements Parcelable {
private static final String ALLOW_ATT_EVENTS = "events";
private static final String SLEEP_TAG = "sleep";
private static final String SLEEP_ATT_MODE = "mode";
+ private static final String SLEEP_ATT_NONE = "none";
private static final String SLEEP_ATT_START_HR = "startHour";
private static final String SLEEP_ATT_START_MIN = "startMin";
@@ -102,6 +108,7 @@ public class ZenModeConfig implements Parcelable {
public int sleepStartMinute; // 0-59
public int sleepEndHour;
public int sleepEndMinute;
+ public boolean sleepNone; // false = priority, true = none
public ComponentName[] conditionComponents;
public Uri[] conditionIds;
public Condition exitCondition;
@@ -120,6 +127,7 @@ public class ZenModeConfig implements Parcelable {
sleepStartMinute = source.readInt();
sleepEndHour = source.readInt();
sleepEndMinute = source.readInt();
+ sleepNone = source.readInt() == 1;
int len = source.readInt();
if (len > 0) {
conditionComponents = new ComponentName[len];
@@ -150,6 +158,7 @@ public class ZenModeConfig implements Parcelable {
dest.writeInt(sleepStartMinute);
dest.writeInt(sleepEndHour);
dest.writeInt(sleepEndMinute);
+ dest.writeInt(sleepNone ? 1 : 0);
if (conditionComponents != null && conditionComponents.length > 0) {
dest.writeInt(conditionComponents.length);
dest.writeTypedArray(conditionComponents, 0);
@@ -177,6 +186,7 @@ public class ZenModeConfig implements Parcelable {
.append(",sleepMode=").append(sleepMode)
.append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute)
.append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute)
+ .append(",sleepNone=").append(sleepNone)
.append(",conditionComponents=")
.append(conditionComponents == null ? null : TextUtils.join(",", conditionComponents))
.append(",conditionIds=")
@@ -209,6 +219,7 @@ public class ZenModeConfig implements Parcelable {
&& other.allowFrom == allowFrom
&& other.allowEvents == allowEvents
&& Objects.equals(other.sleepMode, sleepMode)
+ && other.sleepNone == sleepNone
&& other.sleepStartHour == sleepStartHour
&& other.sleepStartMinute == sleepStartMinute
&& other.sleepEndHour == sleepEndHour
@@ -221,7 +232,7 @@ public class ZenModeConfig implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(allowCalls, allowMessages, allowFrom, allowEvents, sleepMode,
+ return Objects.hash(allowCalls, allowMessages, allowFrom, allowEvents, sleepMode, sleepNone,
sleepStartHour, sleepStartMinute, sleepEndHour, sleepEndMinute,
Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds),
exitCondition, exitConditionComponent);
@@ -297,6 +308,7 @@ public class ZenModeConfig implements Parcelable {
} else if (SLEEP_TAG.equals(tag)) {
final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE);
rt.sleepMode = isValidSleepMode(mode)? mode : null;
+ rt.sleepNone = safeBoolean(parser, SLEEP_ATT_NONE, false);
final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0);
final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0);
final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0);
@@ -340,6 +352,7 @@ public class ZenModeConfig implements Parcelable {
if (sleepMode != null) {
out.attribute(null, SLEEP_ATT_MODE, sleepMode);
}
+ out.attribute(null, SLEEP_ATT_NONE, Boolean.toString(sleepNone));
out.attribute(null, SLEEP_ATT_START_HR, Integer.toString(sleepStartHour));
out.attribute(null, SLEEP_ATT_START_MIN, Integer.toString(sleepStartMinute));
out.attribute(null, SLEEP_ATT_END_HR, Integer.toString(sleepEndHour));
@@ -458,31 +471,48 @@ public class ZenModeConfig implements Parcelable {
downtime.startMinute = sleepStartMinute;
downtime.endHour = sleepEndHour;
downtime.endMinute = sleepEndMinute;
+ downtime.mode = sleepMode;
+ downtime.none = sleepNone;
return downtime;
}
- public static Condition toTimeCondition(int minutesFromNow) {
+ public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) {
final long now = System.currentTimeMillis();
final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS;
- return toTimeCondition(now + millis, minutesFromNow);
+ return toTimeCondition(context, now + millis, minutesFromNow, now, userHandle);
}
- public static Condition toTimeCondition(long time, int minutes) {
- final int num = minutes < 60 ? minutes : Math.round(minutes / 60f);
- final int resId = minutes < 60
- ? com.android.internal.R.plurals.zen_mode_duration_minutes
- : com.android.internal.R.plurals.zen_mode_duration_hours;
- final String caption = Resources.getSystem().getQuantityString(resId, num, num);
+ public static Condition toTimeCondition(Context context, long time, int minutes, long now,
+ int userHandle) {
+ final int num, summaryResId, line1ResId;
+ if (minutes < 60) {
+ // display as minutes
+ num = minutes;
+ summaryResId = R.plurals.zen_mode_duration_minutes_summary;
+ line1ResId = R.plurals.zen_mode_duration_minutes;
+ } else {
+ // display as hours
+ num = Math.round(minutes / 60f);
+ summaryResId = com.android.internal.R.plurals.zen_mode_duration_hours_summary;
+ line1ResId = com.android.internal.R.plurals.zen_mode_duration_hours;
+ }
+ final String skeleton = DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma";
+ final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
+ final CharSequence formattedTime = DateFormat.format(pattern, time);
+ final Resources res = context.getResources();
+ final String summary = res.getQuantityString(summaryResId, num, num, formattedTime);
+ final String line1 = res.getQuantityString(line1ResId, num, num, formattedTime);
+ final String line2 = res.getString(R.string.zen_mode_until, formattedTime);
final Uri id = toCountdownConditionId(time);
- return new Condition(id, caption, "", "", 0, Condition.STATE_TRUE,
+ return new Condition(id, summary, line1, line2, 0, Condition.STATE_TRUE,
Condition.FLAG_RELEVANT_NOW);
}
// For built-in conditions
- private static final String SYSTEM_AUTHORITY = "android";
+ public static final String SYSTEM_AUTHORITY = "android";
// Built-in countdown conditions, e.g. condition://android/countdown/1399917958951
- private static final String COUNTDOWN_PATH = "countdown";
+ public static final String COUNTDOWN_PATH = "countdown";
public static Uri toCountdownConditionId(long time) {
return new Uri.Builder().scheme(Condition.SCHEME)
@@ -508,8 +538,9 @@ public class ZenModeConfig implements Parcelable {
return tryParseCountdownConditionId(conditionId) != 0;
}
- // Built-in downtime conditions, e.g. condition://android/downtime?start=10.00&end=7.00
- private static final String DOWNTIME_PATH = "downtime";
+ // Built-in downtime conditions
+ // e.g. condition://android/downtime?start=10.00&end=7.00&mode=days%3A5%2C6&none=false
+ public static final String DOWNTIME_PATH = "downtime";
public static Uri toDowntimeConditionId(DowntimeInfo downtime) {
return new Uri.Builder().scheme(Condition.SCHEME)
@@ -517,6 +548,8 @@ public class ZenModeConfig implements Parcelable {
.appendPath(DOWNTIME_PATH)
.appendQueryParameter("start", downtime.startHour + "." + downtime.startMinute)
.appendQueryParameter("end", downtime.endHour + "." + downtime.endMinute)
+ .appendQueryParameter("mode", downtime.mode)
+ .appendQueryParameter("none", Boolean.toString(downtime.none))
.build();
}
@@ -534,6 +567,8 @@ public class ZenModeConfig implements Parcelable {
downtime.startMinute = start[1];
downtime.endHour = end[0];
downtime.endMinute = end[1];
+ downtime.mode = conditionId.getQueryParameter("mode");
+ downtime.none = Boolean.toString(true).equals(conditionId.getQueryParameter("none"));
return downtime;
}
@@ -555,6 +590,8 @@ public class ZenModeConfig implements Parcelable {
public int startMinute; // 0-59
public int endHour;
public int endMinute;
+ public String mode;
+ public boolean none;
@Override
public int hashCode() {
@@ -568,7 +605,12 @@ public class ZenModeConfig implements Parcelable {
return startHour == other.startHour
&& startMinute == other.startMinute
&& endHour == other.endHour
- && endMinute == other.endMinute;
+ && endMinute == other.endMinute
+ && Objects.equals(mode, other.mode)
+ && none == other.none;
}
}
+
+ // built-in next alarm conditions
+ public static final String NEXT_ALARM_PATH = "next_alarm";
}
diff --git a/core/java/android/service/trust/ITrustAgentService.aidl b/core/java/android/service/trust/ITrustAgentService.aidl
index bd80a3f..f07d0d0 100644
--- a/core/java/android/service/trust/ITrustAgentService.aidl
+++ b/core/java/android/service/trust/ITrustAgentService.aidl
@@ -15,7 +15,7 @@
*/
package android.service.trust;
-import android.os.Bundle;
+import android.os.PersistableBundle;
import android.service.trust.ITrustAgentServiceCallback;
/**
@@ -25,6 +25,8 @@ import android.service.trust.ITrustAgentServiceCallback;
interface ITrustAgentService {
oneway void onUnlockAttempt(boolean successful);
oneway void onTrustTimeout();
+ oneway void onDeviceLocked();
+ oneway void onDeviceUnlocked();
+ oneway void onConfigure(in List<PersistableBundle> options, IBinder token);
oneway void setCallback(ITrustAgentServiceCallback callback);
- oneway void setTrustAgentFeaturesEnabled(in Bundle options, IBinder token);
}
diff --git a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
index b107bcc..76b2be0 100644
--- a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
+++ b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
@@ -27,5 +27,5 @@ oneway interface ITrustAgentServiceCallback {
void grantTrust(CharSequence message, long durationMs, boolean initiatedByUser);
void revokeTrust();
void setManagingTrust(boolean managingTrust);
- void onSetTrustAgentFeaturesEnabledCompleted(boolean result, IBinder token);
+ void onConfigureCompleted(boolean result, IBinder token);
}
diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java
index 3ef5b37..62fa978 100644
--- a/core/java/android/service/trust/TrustAgentService.java
+++ b/core/java/android/service/trust/TrustAgentService.java
@@ -29,11 +29,14 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.Slog;
+import java.util.List;
+
/**
* A service that notifies the system about whether it believes the environment of the device
* to be trusted.
@@ -86,16 +89,23 @@ public class TrustAgentService extends Service {
*/
public static final String TRUST_AGENT_META_DATA = "android.service.trust.trustagent";
- /**
- * A white list of features that the given trust agent should support when otherwise disabled
- * by device policy.
- * @hide
- */
- public static final String KEY_FEATURES = "trust_agent_features";
-
private static final int MSG_UNLOCK_ATTEMPT = 1;
- private static final int MSG_SET_TRUST_AGENT_FEATURES_ENABLED = 2;
+ private static final int MSG_CONFIGURE = 2;
private static final int MSG_TRUST_TIMEOUT = 3;
+ private static final int MSG_DEVICE_LOCKED = 4;
+ private static final int MSG_DEVICE_UNLOCKED = 5;
+
+ /**
+ * Class containing raw data for a given configuration request.
+ */
+ private static final class ConfigurationData {
+ final IBinder token;
+ final List<PersistableBundle> options;
+ ConfigurationData(List<PersistableBundle> opts, IBinder t) {
+ options = opts;
+ token = t;
+ }
+ }
private ITrustAgentServiceCallback mCallback;
@@ -112,13 +122,12 @@ public class TrustAgentService extends Service {
case MSG_UNLOCK_ATTEMPT:
onUnlockAttempt(msg.arg1 != 0);
break;
- case MSG_SET_TRUST_AGENT_FEATURES_ENABLED:
- Bundle features = msg.peekData();
- IBinder token = (IBinder) msg.obj;
- boolean result = onSetTrustAgentFeaturesEnabled(features);
+ case MSG_CONFIGURE:
+ ConfigurationData data = (ConfigurationData) msg.obj;
+ boolean result = onConfigure(data.options);
try {
synchronized (mLock) {
- mCallback.onSetTrustAgentFeaturesEnabledCompleted(result, token);
+ mCallback.onConfigureCompleted(result, data.token);
}
} catch (RemoteException e) {
onError("calling onSetTrustAgentFeaturesEnabledCompleted()");
@@ -127,6 +136,12 @@ public class TrustAgentService extends Service {
case MSG_TRUST_TIMEOUT:
onTrustTimeout();
break;
+ case MSG_DEVICE_LOCKED:
+ onDeviceLocked();
+ break;
+ case MSG_DEVICE_UNLOCKED:
+ onDeviceUnlocked();
+ break;
}
}
};
@@ -166,28 +181,35 @@ public class TrustAgentService extends Service {
public void onTrustTimeout() {
}
+ /**
+ * Called when the device enters a state where a PIN, pattern or
+ * password must be entered to unlock it.
+ */
+ public void onDeviceLocked() {
+ }
+
+ /**
+ * Called when the device leaves a state where a PIN, pattern or
+ * password must be entered to unlock it.
+ */
+ public void onDeviceUnlocked() {
+ }
+
private void onError(String msg) {
Slog.v(TAG, "Remote exception while " + msg);
}
/**
- * Called when device policy wants to restrict features in the agent in response to
- * {@link DevicePolicyManager#setTrustAgentFeaturesEnabled(ComponentName, ComponentName, java.util.List) }.
- * Agents that support this feature should overload this method and return 'true'.
- *
- * The list of options can be obtained by calling
- * options.getStringArrayList({@link #KEY_FEATURES}). Presence of a feature string in the list
- * means it should be enabled ("white-listed"). Absence of the feature means it should be
- * disabled. An empty list means all features should be disabled.
+ * Called when device policy admin wants to enable specific options for agent in response to
+ * {@link DevicePolicyManager#setKeyguardDisabledFeatures(ComponentName, int)} and
+ * {@link DevicePolicyManager#setTrustAgentConfiguration(ComponentName, ComponentName,
+ * PersistableBundle)}.
+ * <p>Agents that support configuration options should overload this method and return 'true'.
*
- * This function is only called if {@link DevicePolicyManager#KEYGUARD_DISABLE_TRUST_AGENTS} is
- * set.
- *
- * @param options Option feature bundle.
- * @return true if the {@link TrustAgentService} supports this feature.
- * @hide
+ * @param options bundle containing all options or null if none.
+ * @return true if the {@link TrustAgentService} supports configuration options.
*/
- public boolean onSetTrustAgentFeaturesEnabled(Bundle options) {
+ public boolean onConfigure(List<PersistableBundle> options) {
return false;
}
@@ -295,6 +317,22 @@ public class TrustAgentService extends Service {
}
@Override /* Binder API */
+ public void onConfigure(List<PersistableBundle> args, IBinder token) {
+ mHandler.obtainMessage(MSG_CONFIGURE, new ConfigurationData(args, token))
+ .sendToTarget();
+ }
+
+ @Override
+ public void onDeviceLocked() throws RemoteException {
+ mHandler.obtainMessage(MSG_DEVICE_LOCKED).sendToTarget();
+ }
+
+ @Override
+ public void onDeviceUnlocked() throws RemoteException {
+ mHandler.obtainMessage(MSG_DEVICE_UNLOCKED).sendToTarget();
+ }
+
+ @Override /* Binder API */
public void setCallback(ITrustAgentServiceCallback callback) {
synchronized (mLock) {
mCallback = callback;
@@ -313,13 +351,6 @@ public class TrustAgentService extends Service {
}
}
}
-
- @Override /* Binder API */
- public void setTrustAgentFeaturesEnabled(Bundle features, IBinder token) {
- Message msg = mHandler.obtainMessage(MSG_SET_TRUST_AGENT_FEATURES_ENABLED, token);
- msg.setData(features);
- msg.sendToTarget();
- }
}
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 26e9a30..9496b53 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -23,6 +23,7 @@ import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.ViewRootImpl;
import android.view.WindowInsets;
+
import com.android.internal.R;
import com.android.internal.os.HandlerCaller;
import com.android.internal.view.BaseIWindow;
@@ -32,18 +33,17 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.Service;
import android.app.WallpaperManager;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.PowerManager;
import android.os.RemoteException;
import android.util.Log;
import android.view.Display;
@@ -139,7 +139,6 @@ public abstract class WallpaperService extends Service {
boolean mInitializing = true;
boolean mVisible;
- boolean mScreenOn = true;
boolean mReportedVisible;
boolean mDestroyed;
@@ -191,20 +190,10 @@ public abstract class WallpaperService extends Service {
float mPendingYOffsetStep;
boolean mPendingSync;
MotionEvent mPendingMove;
-
- final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
- mScreenOn = true;
- reportVisibility();
- } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
- mScreenOn = false;
- reportVisibility();
- }
- }
- };
-
+
+ DisplayManager mDisplayManager;
+ Display mDisplay;
+
final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() {
{
mRequestedFormat = PixelFormat.RGBX_8888;
@@ -536,8 +525,8 @@ public abstract class WallpaperService extends Service {
out.print(prefix); out.print("mInitializing="); out.print(mInitializing);
out.print(" mDestroyed="); out.println(mDestroyed);
out.print(prefix); out.print("mVisible="); out.print(mVisible);
- out.print(" mScreenOn="); out.print(mScreenOn);
out.print(" mReportedVisible="); out.println(mReportedVisible);
+ out.print(prefix); out.print("mDisplay="); out.println(mDisplay);
out.print(prefix); out.print("mCreated="); out.print(mCreated);
out.print(" mSurfaceCreated="); out.print(mSurfaceCreated);
out.print(" mIsCreating="); out.print(mIsCreating);
@@ -549,7 +538,7 @@ public abstract class WallpaperService extends Service {
out.print(prefix); out.print("mType="); out.print(mType);
out.print(" mWindowFlags="); out.print(mWindowFlags);
out.print(" mCurWindowFlags="); out.println(mCurWindowFlags);
- out.print(" mWindowPrivateFlags="); out.print(mWindowPrivateFlags);
+ out.print(prefix); out.print("mWindowPrivateFlags="); out.print(mWindowPrivateFlags);
out.print(" mCurWindowPrivateFlags="); out.println(mCurWindowPrivateFlags);
out.print(prefix); out.print("mVisibleInsets=");
out.print(mVisibleInsets.toShortString());
@@ -675,7 +664,8 @@ public abstract class WallpaperService extends Service {
com.android.internal.R.style.Animation_Wallpaper;
mInputChannel = new InputChannel();
if (mSession.addToDisplay(mWindow, mWindow.mSeq, mLayout, View.VISIBLE,
- Display.DEFAULT_DISPLAY, mContentInsets, mInputChannel) < 0) {
+ Display.DEFAULT_DISPLAY, mContentInsets, mStableInsets,
+ mInputChannel) < 0) {
Log.w(TAG, "Failed to add window while updating wallpaper surface.");
return;
}
@@ -875,13 +865,10 @@ public abstract class WallpaperService extends Service {
mWindow.setSession(mSession);
- mScreenOn = ((PowerManager)getSystemService(Context.POWER_SERVICE)).isScreenOn();
+ mDisplayManager = (DisplayManager)getSystemService(Context.DISPLAY_SERVICE);
+ mDisplayManager.registerDisplayListener(mDisplayListener, mCaller.getHandler());
+ mDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_SCREEN_ON);
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- registerReceiver(mReceiver, filter);
-
if (DEBUG) Log.v(TAG, "onCreate(): " + this);
onCreate(mSurfaceHolder);
@@ -920,7 +907,8 @@ public abstract class WallpaperService extends Service {
void reportVisibility() {
if (!mDestroyed) {
- boolean visible = mVisible && mScreenOn;
+ boolean visible = mVisible
+ & mDisplay != null && mDisplay.getState() != Display.STATE_OFF;
if (mReportedVisible != visible) {
mReportedVisible = visible;
if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + visible
@@ -1023,7 +1011,11 @@ public abstract class WallpaperService extends Service {
}
mDestroyed = true;
-
+
+ if (mDisplayManager != null) {
+ mDisplayManager.unregisterDisplayListener(mDisplayListener);
+ }
+
if (mVisible) {
mVisible = false;
if (DEBUG) Log.v(TAG, "onVisibilityChanged(false): " + this);
@@ -1034,9 +1026,7 @@ public abstract class WallpaperService extends Service {
if (DEBUG) Log.v(TAG, "onDestroy(): " + this);
onDestroy();
-
- unregisterReceiver(mReceiver);
-
+
if (mCreated) {
try {
if (DEBUG) Log.v(TAG, "Removing window and destroying surface "
@@ -1061,8 +1051,25 @@ public abstract class WallpaperService extends Service {
}
}
}
+
+ private final DisplayListener mDisplayListener = new DisplayListener() {
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (mDisplay.getDisplayId() == displayId) {
+ reportVisibility();
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ }
+ };
}
-
+
class IWallpaperEngineWrapper extends IWallpaperEngine.Stub
implements HandlerCaller.Callback {
private final HandlerCaller mCaller;
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 2853b01..07505a9 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -383,7 +383,6 @@ public class StaticLayout extends Layout {
okBottom = fitBottom;
}
} else {
- final boolean moreChars;
int endPos;
int above, below, top, bottom;
float currentTextWidth;
@@ -395,7 +394,6 @@ public class StaticLayout extends Layout {
top = okTop;
bottom = okBottom;
currentTextWidth = okWidth;
- moreChars = (j + 1 < spanEnd);
} else if (fit != here) {
endPos = fit;
above = fitAscent;
@@ -403,7 +401,6 @@ public class StaticLayout extends Layout {
top = fitTop;
bottom = fitBottom;
currentTextWidth = fitWidth;
- moreChars = (j + 1 < spanEnd);
} else {
// must make progress, so take next character
endPos = here + 1;
@@ -417,15 +414,18 @@ public class StaticLayout extends Layout {
top = fmTop;
bottom = fmBottom;
currentTextWidth = widths[here - paraStart];
- moreChars = (endPos < spanEnd);
}
- v = out(source, here, endPos,
+ int ellipseEnd = endPos;
+ if (mMaximumVisibleLineCount == 1 && ellipsize == TextUtils.TruncateAt.MIDDLE) {
+ ellipseEnd = paraEnd;
+ }
+ v = out(source, here, ellipseEnd,
above, below, top, bottom,
v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, hasTabOrEmoji,
needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
chs, widths, paraStart, ellipsize, ellipsizedWidth,
- currentTextWidth, paint, moreChars);
+ currentTextWidth, paint, true);
here = endPos;
j = here - 1; // restart j-span loop from here, compensating for the j++
@@ -708,7 +708,7 @@ public class StaticLayout extends Layout {
int left = 0, right = len;
float ravail = (avail - ellipsisWidth) / 2;
- for (right = len; right >= 0; right--) {
+ for (right = len; right > 0; right--) {
float w = widths[right - 1 + lineStart - widthStart];
if (w + rsum > ravail) {
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index c19cf32..4725581 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -100,6 +100,9 @@ class TextLine {
tl.mText = null;
tl.mPaint = null;
tl.mDirections = null;
+ tl.mSpanned = null;
+ tl.mTabs = null;
+ tl.mChars = null;
tl.mMetricAffectingSpanSpanSet.recycle();
tl.mCharacterStyleSpanSet.recycle();
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index d515e21..c03f7a6 100755
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -17,6 +17,7 @@
package android.text.format;
import android.content.Context;
+import android.os.UserHandle;
import android.provider.Settings;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -60,27 +61,45 @@ import libcore.icu.LocaleData;
* {@code SimpleDateFormat}.
*/
public class DateFormat {
- /** @deprecated Use a literal {@code '} instead. */
+ /**
+ * @deprecated Use a literal {@code '} instead.
+ * @removed
+ */
@Deprecated
public static final char QUOTE = '\'';
- /** @deprecated Use a literal {@code 'a'} instead. */
+ /**
+ * @deprecated Use a literal {@code 'a'} instead.
+ * @removed
+ */
@Deprecated
public static final char AM_PM = 'a';
- /** @deprecated Use a literal {@code 'a'} instead; 'A' was always equivalent to 'a'. */
+ /**
+ * @deprecated Use a literal {@code 'a'} instead; 'A' was always equivalent to 'a'.
+ * @removed
+ */
@Deprecated
public static final char CAPITAL_AM_PM = 'A';
- /** @deprecated Use a literal {@code 'd'} instead. */
+ /**
+ * @deprecated Use a literal {@code 'd'} instead.
+ * @removed
+ */
@Deprecated
public static final char DATE = 'd';
- /** @deprecated Use a literal {@code 'E'} instead. */
+ /**
+ * @deprecated Use a literal {@code 'E'} instead.
+ * @removed
+ */
@Deprecated
public static final char DAY = 'E';
- /** @deprecated Use a literal {@code 'h'} instead. */
+ /**
+ * @deprecated Use a literal {@code 'h'} instead.
+ * @removed
+ */
@Deprecated
public static final char HOUR = 'h';
@@ -88,31 +107,51 @@ public class DateFormat {
* @deprecated Use a literal {@code 'H'} (for compatibility with {@link SimpleDateFormat}
* and Unicode) or {@code 'k'} (for compatibility with Android releases up to and including
* Jelly Bean MR-1) instead. Note that the two are incompatible.
+ *
+ * @removed
*/
@Deprecated
public static final char HOUR_OF_DAY = 'k';
- /** @deprecated Use a literal {@code 'm'} instead. */
+ /**
+ * @deprecated Use a literal {@code 'm'} instead.
+ * @removed
+ */
@Deprecated
public static final char MINUTE = 'm';
- /** @deprecated Use a literal {@code 'M'} instead. */
+ /**
+ * @deprecated Use a literal {@code 'M'} instead.
+ * @removed
+ */
@Deprecated
public static final char MONTH = 'M';
- /** @deprecated Use a literal {@code 'L'} instead. */
+ /**
+ * @deprecated Use a literal {@code 'L'} instead.
+ * @removed
+ */
@Deprecated
public static final char STANDALONE_MONTH = 'L';
- /** @deprecated Use a literal {@code 's'} instead. */
+ /**
+ * @deprecated Use a literal {@code 's'} instead.
+ * @removed
+ */
@Deprecated
public static final char SECONDS = 's';
- /** @deprecated Use a literal {@code 'z'} instead. */
+ /**
+ * @deprecated Use a literal {@code 'z'} instead.
+ * @removed
+ */
@Deprecated
public static final char TIME_ZONE = 'z';
- /** @deprecated Use a literal {@code 'y'} instead. */
+ /**
+ * @deprecated Use a literal {@code 'y'} instead.
+ * @removed
+ */
@Deprecated
public static final char YEAR = 'y';
@@ -128,8 +167,20 @@ public class DateFormat {
* @return true if 24 hour time format is selected, false otherwise.
*/
public static boolean is24HourFormat(Context context) {
- String value = Settings.System.getString(context.getContentResolver(),
- Settings.System.TIME_12_24);
+ return is24HourFormat(context, UserHandle.myUserId());
+ }
+
+ /**
+ * Returns true if user preference with the given user handle is set to 24-hour format.
+ * @param context the context to use for the content resolver
+ * @param userHandle the user handle of the user to query.
+ * @return true if 24 hour time format is selected, false otherwise.
+ *
+ * @hide
+ */
+ public static boolean is24HourFormat(Context context, int userHandle) {
+ String value = Settings.System.getStringForUser(context.getContentResolver(),
+ Settings.System.TIME_12_24, userHandle);
if (value == null) {
Locale locale = context.getResources().getConfiguration().locale;
@@ -141,7 +192,7 @@ public class DateFormat {
}
java.text.DateFormat natural =
- java.text.DateFormat.getTimeInstance(java.text.DateFormat.LONG, locale);
+ java.text.DateFormat.getTimeInstance(java.text.DateFormat.LONG, locale);
if (natural instanceof SimpleDateFormat) {
SimpleDateFormat sdf = (SimpleDateFormat) natural;
@@ -215,8 +266,19 @@ public class DateFormat {
* @hide
*/
public static String getTimeFormatString(Context context) {
+ return getTimeFormatString(context, UserHandle.myUserId());
+ }
+
+ /**
+ * Returns a String pattern that can be used to format the time according
+ * to the current locale and the user's 12-/24-hour clock preference.
+ * @param context the application context
+ * @param userHandle the user handle of the user to query the format for
+ * @hide
+ */
+ public static String getTimeFormatString(Context context, int userHandle) {
LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale);
- return is24HourFormat(context) ? d.timeFormat_Hm : d.timeFormat_hm;
+ return is24HourFormat(context, userHandle) ? d.timeFormat_Hm : d.timeFormat_hm;
}
/**
@@ -251,9 +313,9 @@ public class DateFormat {
}
/**
- * Gets the current date format stored as a char array. The array will contain
- * 3 elements ({@link #DATE}, {@link #MONTH}, and {@link #YEAR}) in the order
- * specified by the user's format preference. Note that this order is
+ * Gets the current date format stored as a char array. Returns a 3 element
+ * array containing the day ({@code 'd'}), month ({@code 'M'}), and year ({@code 'y'}))
+ * in the order specified by the user's format preference. Note that this order is
* <i>only</i> appropriate for all-numeric dates; spelled-out (MEDIUM and LONG)
* dates will generally contain other punctuation, spaces, or words,
* not just the day, month, and year, and not necessarily in the same
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index b0cbcd2..b467f5a 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -110,6 +110,7 @@ public final class Formatter {
private static final int SECONDS_PER_MINUTE = 60;
private static final int SECONDS_PER_HOUR = 60 * 60;
private static final int SECONDS_PER_DAY = 24 * 60 * 60;
+ private static final int MILLIS_PER_MINUTE = 1000 * 60;
/**
* Returns elapsed time for the given millis, in the following format:
@@ -171,4 +172,24 @@ public final class Formatter {
return context.getString(com.android.internal.R.string.durationSeconds, seconds);
}
}
+
+ /**
+ * Returns elapsed time for the given millis, in the following format:
+ * 1 day 5 hrs; will include at most two units, can go down to minutes precision.
+ * @param context the application context
+ * @param millis the elapsed time in milli seconds
+ * @return the formatted elapsed time
+ * @hide
+ */
+ public static String formatShortElapsedTimeRoundingUpToMinutes(Context context, long millis) {
+ long minutesRoundedUp = (millis + MILLIS_PER_MINUTE - 1) / MILLIS_PER_MINUTE;
+
+ if (minutesRoundedUp == 0) {
+ return context.getString(com.android.internal.R.string.durationMinutes, 0);
+ } else if (minutesRoundedUp == 1) {
+ return context.getString(com.android.internal.R.string.durationMinute, 1);
+ }
+
+ return formatShortElapsedTime(context, minutesRoundedUp * MILLIS_PER_MINUTE);
+ }
}
diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java
index 3c90ab6..0c66709 100644
--- a/core/java/android/text/format/Time.java
+++ b/core/java/android/text/format/Time.java
@@ -48,7 +48,10 @@ import libcore.util.ZoneInfoDB;
* <li>Much of the formatting / parsing assumes ASCII text and is therefore not suitable for
* use with non-ASCII scripts.</li>
* </ul>
+ *
+ * @deprecated Use {@link java.util.GregorianCalendar} instead.
*/
+@Deprecated
public class Time {
private static final String Y_M_D_T_H_M_S_000 = "%Y-%m-%dT%H:%M:%S.000";
private static final String Y_M_D_T_H_M_S_000_Z = "%Y-%m-%dT%H:%M:%S.000Z";
diff --git a/core/java/android/transition/ChangeBounds.java b/core/java/android/transition/ChangeBounds.java
index 0da5fb6..c82587b 100644
--- a/core/java/android/transition/ChangeBounds.java
+++ b/core/java/android/transition/ChangeBounds.java
@@ -16,7 +16,9 @@
package android.transition;
+import android.animation.AnimatorSet;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.PointF;
import android.animation.Animator;
@@ -31,11 +33,12 @@ import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
-import android.util.IntProperty;
import android.util.Property;
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.R;
+
import java.util.Map;
/**
@@ -43,17 +46,20 @@ import java.util.Map;
* the scene change and animates those changes during the transition.
*
* <p>A ChangeBounds transition can be described in a resource file by using the
- * tag <code>changeBounds</code>, along with the other standard
+ * tag <code>changeBounds</code>, using its attributes of
+ * {@link android.R.styleable#ChangeBounds} along with the other standard
* attributes of {@link android.R.styleable#Transition}.</p>
*/
public class ChangeBounds extends Transition {
private static final String PROPNAME_BOUNDS = "android:changeBounds:bounds";
+ private static final String PROPNAME_CLIP = "android:changeBounds:clip";
private static final String PROPNAME_PARENT = "android:changeBounds:parent";
private static final String PROPNAME_WINDOW_X = "android:changeBounds:windowX";
private static final String PROPNAME_WINDOW_Y = "android:changeBounds:windowY";
private static final String[] sTransitionProperties = {
PROPNAME_BOUNDS,
+ PROPNAME_CLIP,
PROPNAME_PARENT,
PROPNAME_WINDOW_X,
PROPNAME_WINDOW_Y
@@ -77,6 +83,83 @@ public class ChangeBounds extends Transition {
}
};
+ private static final Property<ViewBounds, PointF> TOP_LEFT_PROPERTY =
+ new Property<ViewBounds, PointF>(PointF.class, "topLeft") {
+ @Override
+ public void set(ViewBounds viewBounds, PointF topLeft) {
+ viewBounds.setTopLeft(topLeft);
+ }
+
+ @Override
+ public PointF get(ViewBounds viewBounds) {
+ return null;
+ }
+ };
+
+ private static final Property<ViewBounds, PointF> BOTTOM_RIGHT_PROPERTY =
+ new Property<ViewBounds, PointF>(PointF.class, "bottomRight") {
+ @Override
+ public void set(ViewBounds viewBounds, PointF bottomRight) {
+ viewBounds.setBottomRight(bottomRight);
+ }
+
+ @Override
+ public PointF get(ViewBounds viewBounds) {
+ return null;
+ }
+ };
+
+ private static final Property<View, PointF> BOTTOM_RIGHT_ONLY_PROPERTY =
+ new Property<View, PointF>(PointF.class, "bottomRight") {
+ @Override
+ public void set(View view, PointF bottomRight) {
+ int left = view.getLeft();
+ int top = view.getTop();
+ int right = Math.round(bottomRight.x);
+ int bottom = Math.round(bottomRight.y);
+ view.setLeftTopRightBottom(left, top, right, bottom);
+ }
+
+ @Override
+ public PointF get(View view) {
+ return null;
+ }
+ };
+
+ private static final Property<View, PointF> TOP_LEFT_ONLY_PROPERTY =
+ new Property<View, PointF>(PointF.class, "topLeft") {
+ @Override
+ public void set(View view, PointF topLeft) {
+ int left = Math.round(topLeft.x);
+ int top = Math.round(topLeft.y);
+ int right = view.getRight();
+ int bottom = view.getBottom();
+ view.setLeftTopRightBottom(left, top, right, bottom);
+ }
+
+ @Override
+ public PointF get(View view) {
+ return null;
+ }
+ };
+
+ private static final Property<View, PointF> POSITION_PROPERTY =
+ new Property<View, PointF>(PointF.class, "position") {
+ @Override
+ public void set(View view, PointF topLeft) {
+ int left = Math.round(topLeft.x);
+ int top = Math.round(topLeft.y);
+ int right = left + view.getWidth();
+ int bottom = top + view.getHeight();
+ view.setLeftTopRightBottom(left, top, right, bottom);
+ }
+
+ @Override
+ public PointF get(View view) {
+ return null;
+ }
+ };
+
int[] tempLocation = new int[2];
boolean mResizeClip = false;
boolean mReparent = false;
@@ -88,6 +171,11 @@ public class ChangeBounds extends Transition {
public ChangeBounds(Context context, AttributeSet attrs) {
super(context, attrs);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ChangeBounds);
+ boolean resizeClip = a.getBoolean(R.styleable.ChangeBounds_resizeClip, false);
+ a.recycle();
+ setResizeClip(resizeClip);
}
@Override
@@ -95,11 +183,37 @@ public class ChangeBounds extends Transition {
return sTransitionProperties;
}
+ /**
+ * When <code>resizeClip</code> is true, ChangeBounds resizes the view using the clipBounds
+ * instead of changing the dimensions of the view during the animation. When
+ * <code>resizeClip</code> is false, ChangeBounds resizes the View by changing its dimensions.
+ *
+ * <p>When resizeClip is set to true, the clip bounds is modified by ChangeBounds. Therefore,
+ * {@link android.transition.ChangeClipBounds} is not compatible with ChangeBounds
+ * in this mode.</p>
+ *
+ * @param resizeClip Used to indicate whether the view bounds should be modified or the
+ * clip bounds should be modified by ChangeBounds.
+ * @see android.view.View#setClipBounds(android.graphics.Rect)
+ * @attr ref android.R.styleable#ChangeBounds_resizeClip
+ */
public void setResizeClip(boolean resizeClip) {
mResizeClip = resizeClip;
}
/**
+ * Returns true when the ChangeBounds will resize by changing the clip bounds during the
+ * view animation or false when bounds are changed. The default value is false.
+ *
+ * @return true when the ChangeBounds will resize by changing the clip bounds during the
+ * view animation or false when bounds are changed. The default value is false.
+ * @attr ref android.R.styleable#ChangeBounds_resizeClip
+ */
+ public boolean getResizeClip() {
+ return mResizeClip;
+ }
+
+ /**
* Setting this flag tells ChangeBounds to track the before/after parent
* of every view using this transition. The flag is not enabled by
* default because it requires the parent instances to be the same
@@ -127,6 +241,9 @@ public class ChangeBounds extends Transition {
values.values.put(PROPNAME_WINDOW_X, tempLocation[0]);
values.values.put(PROPNAME_WINDOW_Y, tempLocation[1]);
}
+ if (mResizeClip) {
+ values.values.put(PROPNAME_CLIP, view.getClipBounds());
+ }
}
}
@@ -170,158 +287,149 @@ public class ChangeBounds extends Transition {
if (parentMatches(startParent, endParent)) {
Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
- int startLeft = startBounds.left;
- int endLeft = endBounds.left;
- int startTop = startBounds.top;
- int endTop = endBounds.top;
- int startRight = startBounds.right;
- int endRight = endBounds.right;
- int startBottom = startBounds.bottom;
- int endBottom = endBounds.bottom;
- int startWidth = startRight - startLeft;
- int startHeight = startBottom - startTop;
- int endWidth = endRight - endLeft;
- int endHeight = endBottom - endTop;
+ final int startLeft = startBounds.left;
+ final int endLeft = endBounds.left;
+ final int startTop = startBounds.top;
+ final int endTop = endBounds.top;
+ final int startRight = startBounds.right;
+ final int endRight = endBounds.right;
+ final int startBottom = startBounds.bottom;
+ final int endBottom = endBounds.bottom;
+ final int startWidth = startRight - startLeft;
+ final int startHeight = startBottom - startTop;
+ final int endWidth = endRight - endLeft;
+ final int endHeight = endBottom - endTop;
+ Rect startClip = (Rect) startValues.values.get(PROPNAME_CLIP);
+ Rect endClip = (Rect) endValues.values.get(PROPNAME_CLIP);
int numChanges = 0;
if ((startWidth != 0 && startHeight != 0) || (endWidth != 0 && endHeight != 0)) {
if (startLeft != endLeft || startTop != endTop) ++numChanges;
if (startRight != endRight || startBottom != endBottom) ++numChanges;
}
+ if ((startClip != null && !startClip.equals(endClip)) ||
+ (startClip == null && endClip != null)) {
+ ++numChanges;
+ }
if (numChanges > 0) {
+ Animator anim;
if (!mResizeClip) {
- Animator anim;
- if (startWidth == endWidth && startHeight == endHeight) {
- view.offsetLeftAndRight(startLeft - view.getLeft());
- view.offsetTopAndBottom(startTop - view.getTop());
- Path positionPath = getPathMotion().getPath(0, 0, endLeft - startLeft,
- endTop - startTop);
- anim = ObjectAnimator.ofInt(view, new HorizontalOffsetProperty(),
- new VerticalOffsetProperty(), positionPath);
- } else {
- if (startLeft != endLeft) view.setLeft(startLeft);
- if (startTop != endTop) view.setTop(startTop);
- if (startRight != endRight) view.setRight(startRight);
- if (startBottom != endBottom) view.setBottom(startBottom);
- ObjectAnimator topLeftAnimator = null;
- if (startLeft != endLeft || startTop != endTop) {
+ view.setLeftTopRightBottom(startLeft, startTop, startRight, startBottom);
+ if (numChanges == 2) {
+ if (startWidth == endWidth && startHeight == endHeight) {
+ Path topLeftPath = getPathMotion().getPath(startLeft, startTop, endLeft,
+ endTop);
+ anim = ObjectAnimator.ofObject(view, POSITION_PROPERTY, null,
+ topLeftPath);
+ } else {
+ final ViewBounds viewBounds = new ViewBounds(view);
Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
endLeft, endTop);
- topLeftAnimator = ObjectAnimator
- .ofInt(view, "left", "top", topLeftPath);
- }
- ObjectAnimator bottomRightAnimator = null;
- if (startRight != endRight || startBottom != endBottom) {
+ ObjectAnimator topLeftAnimator = ObjectAnimator
+ .ofObject(viewBounds, TOP_LEFT_PROPERTY, null, topLeftPath);
+
Path bottomRightPath = getPathMotion().getPath(startRight, startBottom,
endRight, endBottom);
- bottomRightAnimator = ObjectAnimator.ofInt(view, "right", "bottom",
- bottomRightPath);
+ ObjectAnimator bottomRightAnimator = ObjectAnimator.ofObject(viewBounds,
+ BOTTOM_RIGHT_PROPERTY, null, bottomRightPath);
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(topLeftAnimator, bottomRightAnimator);
+ anim = set;
+ set.addListener(new AnimatorListenerAdapter() {
+ // We need a strong reference to viewBounds until the
+ // animator ends.
+ private ViewBounds mViewBounds = viewBounds;
+ });
}
- anim = TransitionUtils.mergeAnimators(topLeftAnimator,
- bottomRightAnimator);
- }
- if (view.getParent() instanceof ViewGroup) {
- final ViewGroup parent = (ViewGroup) view.getParent();
- parent.suppressLayout(true);
- TransitionListener transitionListener = new TransitionListenerAdapter() {
- boolean mCanceled = false;
-
- @Override
- public void onTransitionCancel(Transition transition) {
- parent.suppressLayout(false);
- mCanceled = true;
- }
-
- @Override
- public void onTransitionEnd(Transition transition) {
- if (!mCanceled) {
- parent.suppressLayout(false);
- }
- }
-
- @Override
- public void onTransitionPause(Transition transition) {
- parent.suppressLayout(false);
- }
-
- @Override
- public void onTransitionResume(Transition transition) {
- parent.suppressLayout(true);
- }
- };
- addListener(transitionListener);
+ } else if (startLeft != endLeft || startTop != endTop) {
+ Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
+ endLeft, endTop);
+ anim = ObjectAnimator.ofObject(view, TOP_LEFT_ONLY_PROPERTY, null,
+ topLeftPath);
+ } else {
+ Path bottomRight = getPathMotion().getPath(startRight, startBottom,
+ endRight, endBottom);
+ anim = ObjectAnimator.ofObject(view, BOTTOM_RIGHT_ONLY_PROPERTY, null,
+ bottomRight);
}
- return anim;
} else {
- if (startWidth != endWidth) view.setRight(endLeft +
- Math.max(startWidth, endWidth));
- if (startHeight != endHeight) view.setBottom(endTop +
- Math.max(startHeight, endHeight));
- // TODO: don't clobber TX/TY
- if (startLeft != endLeft) view.setTranslationX(startLeft - endLeft);
- if (startTop != endTop) view.setTranslationY(startTop - endTop);
- // Animate location with translationX/Y and size with clip bounds
- float transXDelta = endLeft - startLeft;
- float transYDelta = endTop - startTop;
- int widthDelta = endWidth - startWidth;
- int heightDelta = endHeight - startHeight;
- numChanges = 0;
- if (transXDelta != 0) numChanges++;
- if (transYDelta != 0) numChanges++;
- if (widthDelta != 0 || heightDelta != 0) numChanges++;
- ObjectAnimator translationAnimator = null;
- if (transXDelta != 0 || transYDelta != 0) {
- Path topLeftPath = getPathMotion().getPath(0, 0, transXDelta, transYDelta);
- translationAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
- View.TRANSLATION_Y, topLeftPath);
+ int maxWidth = Math.max(startWidth, endWidth);
+ int maxHeight = Math.max(startHeight, endHeight);
+
+ view.setLeftTopRightBottom(startLeft, startTop, startLeft + maxWidth,
+ startTop + maxHeight);
+
+ ObjectAnimator positionAnimator = null;
+ if (startLeft != endLeft || startTop != endTop) {
+ Path topLeftPath = getPathMotion().getPath(startLeft, startTop, endLeft,
+ endTop);
+ positionAnimator = ObjectAnimator.ofObject(view, POSITION_PROPERTY, null,
+ topLeftPath);
+ }
+ final Rect finalClip = endClip;
+ if (startClip == null) {
+ startClip = new Rect(0, 0, startWidth, startHeight);
+ }
+ if (endClip == null) {
+ endClip = new Rect(0, 0, endWidth, endHeight);
}
ObjectAnimator clipAnimator = null;
- if (widthDelta != 0 || heightDelta != 0) {
- Rect tempStartBounds = new Rect(0, 0, startWidth, startHeight);
- Rect tempEndBounds = new Rect(0, 0, endWidth, endHeight);
+ if (!startClip.equals(endClip)) {
+ view.setClipBounds(startClip);
clipAnimator = ObjectAnimator.ofObject(view, "clipBounds", sRectEvaluator,
- tempStartBounds, tempEndBounds);
- }
- Animator anim = TransitionUtils.mergeAnimators(translationAnimator,
- clipAnimator);
- if (view.getParent() instanceof ViewGroup) {
- final ViewGroup parent = (ViewGroup) view.getParent();
- parent.suppressLayout(true);
- TransitionListener transitionListener = new TransitionListenerAdapter() {
- boolean mCanceled = false;
+ startClip, endClip);
+ clipAnimator.addListener(new AnimatorListenerAdapter() {
+ private boolean mIsCanceled;
@Override
- public void onTransitionCancel(Transition transition) {
- parent.suppressLayout(false);
- mCanceled = true;
+ public void onAnimationCancel(Animator animation) {
+ mIsCanceled = true;
}
@Override
- public void onTransitionEnd(Transition transition) {
- if (!mCanceled) {
- parent.suppressLayout(false);
+ public void onAnimationEnd(Animator animation) {
+ if (!mIsCanceled) {
+ view.setClipBounds(finalClip);
+ view.setLeftTopRightBottom(endLeft, endTop, endRight,
+ endBottom);
}
}
+ });
+ }
+ anim = TransitionUtils.mergeAnimators(positionAnimator,
+ clipAnimator);
+ }
+ if (view.getParent() instanceof ViewGroup) {
+ final ViewGroup parent = (ViewGroup) view.getParent();
+ parent.suppressLayout(true);
+ TransitionListener transitionListener = new TransitionListenerAdapter() {
+ boolean mCanceled = false;
- @Override
- public void onTransitionPause(Transition transition) {
+ @Override
+ public void onTransitionCancel(Transition transition) {
+ parent.suppressLayout(false);
+ mCanceled = true;
+ }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ if (!mCanceled) {
parent.suppressLayout(false);
}
+ }
+
+ @Override
+ public void onTransitionPause(Transition transition) {
+ parent.suppressLayout(false);
+ }
- @Override
- public void onTransitionResume(Transition transition) {
- parent.suppressLayout(true);
- }
- };
- addListener(transitionListener);
- }
- anim.addListener(new AnimatorListenerAdapter() {
@Override
- public void onAnimationEnd(Animator animation) {
- view.setClipBounds(null);
+ public void onTransitionResume(Transition transition) {
+ parent.suppressLayout(true);
}
- });
- return anim;
+ };
+ addListener(transitionListener);
}
+ return anim;
}
} else {
int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X);
@@ -357,47 +465,41 @@ public class ChangeBounds extends Transition {
return null;
}
- private abstract static class OffsetProperty extends IntProperty<View> {
- int mPreviousValue;
-
- public OffsetProperty(String name) {
- super(name);
- }
-
- @Override
- public void setValue(View view, int value) {
- int offset = value - mPreviousValue;
- offsetBy(view, offset);
- mPreviousValue = value;
- }
-
- @Override
- public Integer get(View object) {
- return null;
- }
-
- protected abstract void offsetBy(View view, int by);
- }
-
- private static class HorizontalOffsetProperty extends OffsetProperty {
- public HorizontalOffsetProperty() {
- super("offsetLeftAndRight");
+ private static class ViewBounds {
+ private int mLeft;
+ private int mTop;
+ private int mRight;
+ private int mBottom;
+ private boolean mIsTopLeftSet;
+ private boolean mIsBottomRightSet;
+ private View mView;
+
+ public ViewBounds(View view) {
+ mView = view;
}
- @Override
- protected void offsetBy(View view, int by) {
- view.offsetLeftAndRight(by);
+ public void setTopLeft(PointF topLeft) {
+ mLeft = Math.round(topLeft.x);
+ mTop = Math.round(topLeft.y);
+ mIsTopLeftSet = true;
+ if (mIsBottomRightSet) {
+ setLeftTopRightBottom();
+ }
}
- }
- private static class VerticalOffsetProperty extends OffsetProperty {
- public VerticalOffsetProperty() {
- super("offsetTopAndBottom");
+ public void setBottomRight(PointF bottomRight) {
+ mRight = Math.round(bottomRight.x);
+ mBottom = Math.round(bottomRight.y);
+ mIsBottomRightSet = true;
+ if (mIsTopLeftSet) {
+ setLeftTopRightBottom();
+ }
}
- @Override
- protected void offsetBy(View view, int by) {
- view.offsetTopAndBottom(by);
+ private void setLeftTopRightBottom() {
+ mView.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
+ mIsTopLeftSet = false;
+ mIsBottomRightSet = false;
}
}
}
diff --git a/core/java/android/transition/ChangeTransform.java b/core/java/android/transition/ChangeTransform.java
index 3fd28a6..9749121 100644
--- a/core/java/android/transition/ChangeTransform.java
+++ b/core/java/android/transition/ChangeTransform.java
@@ -17,11 +17,14 @@ package android.transition;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.FloatArrayEvaluator;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
+import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Property;
import android.view.GhostView;
@@ -56,16 +59,35 @@ public class ChangeTransform extends Transition {
PROPNAME_PARENT_MATRIX,
};
- private static final Property<View, Matrix> ANIMATION_MATRIX_PROPERTY =
- new Property<View, Matrix>(Matrix.class, "animationMatrix") {
+ /**
+ * This property sets the animation matrix properties that are not translations.
+ */
+ private static final Property<PathAnimatorMatrix, float[]> NON_TRANSLATIONS_PROPERTY =
+ new Property<PathAnimatorMatrix, float[]>(float[].class, "nonTranslations") {
@Override
- public Matrix get(View object) {
+ public float[] get(PathAnimatorMatrix object) {
return null;
}
@Override
- public void set(View object, Matrix value) {
- object.setAnimationMatrix(value);
+ public void set(PathAnimatorMatrix object, float[] value) {
+ object.setValues(value);
+ }
+ };
+
+ /**
+ * This property sets the translation animation matrix properties.
+ */
+ private static final Property<PathAnimatorMatrix, PointF> TRANSLATIONS_PROPERTY =
+ new Property<PathAnimatorMatrix, PointF>(PointF.class, "translations") {
+ @Override
+ public PointF get(PathAnimatorMatrix object) {
+ return null;
+ }
+
+ @Override
+ public void set(PathAnimatorMatrix object, PointF value) {
+ object.setTranslation(value);
}
};
@@ -261,8 +283,23 @@ public class ChangeTransform extends Transition {
final View view = endValues.view;
setIdentityTransforms(view);
- ObjectAnimator animator = ObjectAnimator.ofObject(view, ANIMATION_MATRIX_PROPERTY,
- new TransitionUtils.MatrixEvaluator(), startMatrix, endMatrix);
+ final float[] startMatrixValues = new float[9];
+ startMatrix.getValues(startMatrixValues);
+ final float[] endMatrixValues = new float[9];
+ endMatrix.getValues(endMatrixValues);
+ final PathAnimatorMatrix pathAnimatorMatrix =
+ new PathAnimatorMatrix(view, startMatrixValues);
+
+ PropertyValuesHolder valuesProperty = PropertyValuesHolder.ofObject(
+ NON_TRANSLATIONS_PROPERTY, new FloatArrayEvaluator(new float[9]),
+ startMatrixValues, endMatrixValues);
+ Path path = getPathMotion().getPath(startMatrixValues[Matrix.MTRANS_X],
+ startMatrixValues[Matrix.MTRANS_Y], endMatrixValues[Matrix.MTRANS_X],
+ endMatrixValues[Matrix.MTRANS_Y]);
+ PropertyValuesHolder translationProperty = PropertyValuesHolder.ofObject(
+ TRANSLATIONS_PROPERTY, null, path);
+ ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(pathAnimatorMatrix,
+ valuesProperty, translationProperty);
final Matrix finalEndMatrix = endMatrix;
@@ -285,14 +322,13 @@ public class ChangeTransform extends Transition {
view.setTagInternal(R.id.parentMatrix, null);
}
}
- ANIMATION_MATRIX_PROPERTY.set(view, null);
+ view.setAnimationMatrix(null);
transforms.restore(view);
}
@Override
public void onAnimationPause(Animator animation) {
- ValueAnimator animator = (ValueAnimator) animation;
- Matrix currentMatrix = (Matrix) animator.getAnimatedValue();
+ Matrix currentMatrix = pathAnimatorMatrix.getMatrix();
setCurrentMatrix(currentMatrix);
}
@@ -340,7 +376,7 @@ public class ChangeTransform extends Transition {
while (outerTransition.mParent != null) {
outerTransition = outerTransition.mParent;
}
- GhostListener listener = new GhostListener(view, ghostView, endMatrix);
+ GhostListener listener = new GhostListener(view, startValues.view, ghostView);
outerTransition.addListener(listener);
if (startValues.view != endValues.view) {
@@ -430,13 +466,13 @@ public class ChangeTransform extends Transition {
private static class GhostListener extends Transition.TransitionListenerAdapter {
private View mView;
+ private View mStartView;
private GhostView mGhostView;
- private Matrix mEndMatrix;
- public GhostListener(View view, GhostView ghostView, Matrix endMatrix) {
+ public GhostListener(View view, View startView, GhostView ghostView) {
mView = view;
+ mStartView = startView;
mGhostView = ghostView;
- mEndMatrix = endMatrix;
}
@Override
@@ -445,6 +481,7 @@ public class ChangeTransform extends Transition {
GhostView.removeGhost(mView);
mView.setTagInternal(R.id.transitionTransform, null);
mView.setTagInternal(R.id.parentMatrix, null);
+ mStartView.setTransitionAlpha(1);
}
@Override
@@ -457,4 +494,47 @@ public class ChangeTransform extends Transition {
mGhostView.setVisibility(View.VISIBLE);
}
}
+
+ /**
+ * PathAnimatorMatrix allows the translations and the rest of the matrix to be set
+ * separately. This allows the PathMotion to affect the translations while scale
+ * and rotation are evaluated separately.
+ */
+ private static class PathAnimatorMatrix {
+ private final Matrix mMatrix = new Matrix();
+ private final View mView;
+ private final float[] mValues;
+ private float mTranslationX;
+ private float mTranslationY;
+
+ public PathAnimatorMatrix(View view, float[] values) {
+ mView = view;
+ mValues = values.clone();
+ mTranslationX = mValues[Matrix.MTRANS_X];
+ mTranslationY = mValues[Matrix.MTRANS_Y];
+ setAnimationMatrix();
+ }
+
+ public void setValues(float[] values) {
+ System.arraycopy(values, 0, mValues, 0, values.length);
+ setAnimationMatrix();
+ }
+
+ public void setTranslation(PointF translation) {
+ mTranslationX = translation.x;
+ mTranslationY = translation.y;
+ setAnimationMatrix();
+ }
+
+ private void setAnimationMatrix() {
+ mValues[Matrix.MTRANS_X] = mTranslationX;
+ mValues[Matrix.MTRANS_Y] = mTranslationY;
+ mMatrix.setValues(mValues);
+ mView.setAnimationMatrix(mMatrix);
+ }
+
+ public Matrix getMatrix() {
+ return mMatrix;
+ }
+ }
}
diff --git a/core/java/android/transition/SidePropagation.java b/core/java/android/transition/SidePropagation.java
index 46df2a8..5dd1fff 100644
--- a/core/java/android/transition/SidePropagation.java
+++ b/core/java/android/transition/SidePropagation.java
@@ -43,8 +43,8 @@ public class SidePropagation extends VisibilityPropagation {
* farther from the edge. The default is {@link Gravity#BOTTOM}.
*
* @param side The side that is used to calculate the transition propagation. Must be one of
- * {@link Gravity#LEFT}, {@link Gravity#TOP}, {@link Gravity#RIGHT}, or
- * {@link Gravity#BOTTOM}.
+ * {@link Gravity#LEFT}, {@link Gravity#TOP}, {@link Gravity#RIGHT},
+ * {@link Gravity#BOTTOM}, {@link Gravity#START}, or {@link Gravity#END}.
*/
public void setSide(int side) {
mSide = side;
@@ -105,7 +105,7 @@ public class SidePropagation extends VisibilityPropagation {
epicenterY = (top + bottom) / 2;
}
- float distance = distance(viewCenterX, viewCenterY, epicenterX, epicenterY,
+ float distance = distance(sceneRoot, viewCenterX, viewCenterY, epicenterX, epicenterY,
left, top, right, bottom);
float maxDistance = getMaxDistance(sceneRoot);
float distanceFraction = distance/maxDistance;
@@ -118,10 +118,20 @@ public class SidePropagation extends VisibilityPropagation {
return Math.round(duration * directionMultiplier / mPropagationSpeed * distanceFraction);
}
- private int distance(int viewX, int viewY, int epicenterX, int epicenterY,
+ private int distance(View sceneRoot, int viewX, int viewY, int epicenterX, int epicenterY,
int left, int top, int right, int bottom) {
+ final int side;
+ if (mSide == Gravity.START) {
+ final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ side = isRtl ? Gravity.RIGHT : Gravity.LEFT;
+ } else if (mSide == Gravity.END) {
+ final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ side = isRtl ? Gravity.LEFT : Gravity.RIGHT;
+ } else {
+ side = mSide;
+ }
int distance = 0;
- switch (mSide) {
+ switch (side) {
case Gravity.LEFT:
distance = right - viewX + Math.abs(epicenterY - viewY);
break;
@@ -142,6 +152,8 @@ public class SidePropagation extends VisibilityPropagation {
switch (mSide) {
case Gravity.LEFT:
case Gravity.RIGHT:
+ case Gravity.START:
+ case Gravity.END:
return sceneRoot.getWidth();
default:
return sceneRoot.getHeight();
diff --git a/core/java/android/transition/Slide.java b/core/java/android/transition/Slide.java
index ae2e4aa..be1d907 100644
--- a/core/java/android/transition/Slide.java
+++ b/core/java/android/transition/Slide.java
@@ -76,6 +76,20 @@ public class Slide extends Visibility {
}
};
+ private static final CalculateSlide sCalculateStart = new CalculateSlideHorizontal() {
+ @Override
+ public float getGoneX(ViewGroup sceneRoot, View view) {
+ final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ final float x;
+ if (isRtl) {
+ x = view.getTranslationX() + sceneRoot.getWidth();
+ } else {
+ x = view.getTranslationX() - sceneRoot.getWidth();
+ }
+ return x;
+ }
+ };
+
private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
@Override
public float getGoneY(ViewGroup sceneRoot, View view) {
@@ -90,6 +104,20 @@ public class Slide extends Visibility {
}
};
+ private static final CalculateSlide sCalculateEnd = new CalculateSlideHorizontal() {
+ @Override
+ public float getGoneX(ViewGroup sceneRoot, View view) {
+ final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ final float x;
+ if (isRtl) {
+ x = view.getTranslationX() - sceneRoot.getWidth();
+ } else {
+ x = view.getTranslationX() + sceneRoot.getWidth();
+ }
+ return x;
+ }
+ };
+
private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
@Override
public float getGoneY(ViewGroup sceneRoot, View view) {
@@ -144,7 +172,8 @@ public class Slide extends Visibility {
*
* @param slideEdge The edge of the scene to use for Views appearing and disappearing. One of
* {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
- * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}.
+ * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
+ * {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
* @attr ref android.R.styleable#Slide_slideEdge
*/
public void setSlideEdge(int slideEdge) {
@@ -161,6 +190,12 @@ public class Slide extends Visibility {
case Gravity.BOTTOM:
mSlideCalculator = sCalculateBottom;
break;
+ case Gravity.START:
+ mSlideCalculator = sCalculateStart;
+ break;
+ case Gravity.END:
+ mSlideCalculator = sCalculateEnd;
+ break;
default:
throw new IllegalArgumentException("Invalid slide direction");
}
@@ -175,7 +210,8 @@ public class Slide extends Visibility {
*
* @return the edge of the scene to use for Views appearing and disappearing. One of
* {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
- * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}.
+ * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
+ * {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
* @attr ref android.R.styleable#Slide_slideEdge
*/
public int getSlideEdge() {
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index 6dede46..2705bcf 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -84,8 +84,8 @@ import com.android.internal.R;
*
* <p>Custom transition classes may be instantiated with a <code>transition</code> tag:</p>
* <pre>&lt;transition class="my.app.transition.CustomTransition"/></pre>
- * <p>Custom transition classes loaded from XML must have a public nullary (no argument)
- * constructor.</p>
+ * <p>Custom transition classes loaded from XML should have a public constructor taking
+ * a {@link android.content.Context} and {@link android.util.AttributeSet}.</p>
*
* <p>Note that attributes for the transition are not required, just as they are
* optional when declared in code; Transitions created from XML resources will use
@@ -955,7 +955,7 @@ public abstract class Transition implements Cloneable {
* Views with different IDs, or no IDs whatsoever, will be ignored.
*
* <p>Note that using ids to specify targets implies that ids should be unique
- * within the view hierarchy underneat the scene root.</p>
+ * within the view hierarchy underneath the scene root.</p>
*
* @see View#getId()
* @param targetId The id of a target view, must be a positive number.
@@ -1790,6 +1790,10 @@ public abstract class Transition implements Cloneable {
private static boolean isValueChanged(TransitionValues oldValues, TransitionValues newValues,
String key) {
+ if (oldValues.values.containsKey(key) != newValues.values.containsKey(key)) {
+ // The transition didn't care about this particular value, so we don't care, either.
+ return false;
+ }
Object oldValue = oldValues.values.get(key);
Object newValue = newValues.values.get(key);
boolean changed;
diff --git a/core/java/android/transition/TransitionUtils.java b/core/java/android/transition/TransitionUtils.java
index 03423ff..49ceb3b 100644
--- a/core/java/android/transition/TransitionUtils.java
+++ b/core/java/android/transition/TransitionUtils.java
@@ -22,8 +22,10 @@ import android.animation.TypeEvaluator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
@@ -109,6 +111,35 @@ public class TransitionUtils {
}
/**
+ * Get a copy of bitmap of given drawable, return null if intrinsic size is zero
+ */
+ public static Bitmap createDrawableBitmap(Drawable drawable) {
+ int width = drawable.getIntrinsicWidth();
+ int height = drawable.getIntrinsicHeight();
+ if (width <= 0 || height <= 0) {
+ return null;
+ }
+ float scale = Math.min(1f, ((float)MAX_IMAGE_SIZE) / (width * height));
+ if (drawable instanceof BitmapDrawable && scale == 1f) {
+ // return same bitmap if scale down not needed
+ return ((BitmapDrawable) drawable).getBitmap();
+ }
+ int bitmapWidth = (int) (width * scale);
+ int bitmapHeight = (int) (height * scale);
+ Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ Rect existingBounds = drawable.getBounds();
+ int left = existingBounds.left;
+ int top = existingBounds.top;
+ int right = existingBounds.right;
+ int bottom = existingBounds.bottom;
+ drawable.setBounds(0, 0, bitmapWidth, bitmapHeight);
+ drawable.draw(canvas);
+ drawable.setBounds(left, top, right, bottom);
+ return bitmap;
+ }
+
+ /**
* Creates a Bitmap of the given view, using the Matrix matrix to transform to the local
* coordinates. <code>matrix</code> will be modified during the bitmap creation.
*
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index f58291f..8779229 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -63,6 +63,7 @@ public abstract class Visibility extends Transition {
private static final String[] sTransitionProperties = {
PROPNAME_VISIBILITY,
+ PROPNAME_PARENT,
};
private static class VisibilityInfo {
@@ -484,12 +485,19 @@ public abstract class Visibility extends Transition {
@Override
boolean areValuesChanged(TransitionValues oldValues, TransitionValues newValues) {
- VisibilityInfo changeInfo = getVisibilityChangeInfo(oldValues, newValues);
if (oldValues == null && newValues == null) {
return false;
}
+ if (oldValues != null && newValues != null &&
+ newValues.values.containsKey(PROPNAME_VISIBILITY) !=
+ oldValues.values.containsKey(PROPNAME_VISIBILITY)) {
+ // The transition wasn't targeted in either the start or end, so it couldn't
+ // have changed.
+ return false;
+ }
+ VisibilityInfo changeInfo = getVisibilityChangeInfo(oldValues, newValues);
return changeInfo.visibilityChange && (changeInfo.startVisibility == View.VISIBLE ||
- changeInfo.endVisibility == View.VISIBLE);
+ changeInfo.endVisibility == View.VISIBLE);
}
/**
diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java
index 7c9861f..6ed3885 100644
--- a/core/java/android/util/ArrayMap.java
+++ b/core/java/android/util/ArrayMap.java
@@ -255,7 +255,10 @@ public final class ArrayMap<K, V> implements Map<K, V> {
}
private ArrayMap(boolean immutable) {
- mHashes = EmptyArray.INT;
+ // If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS
+ // instance instead of the usual EmptyArray.INT. The reference
+ // is checked later to see if the array is allowed to grow.
+ mHashes = immutable ? EMPTY_IMMUTABLE_INTS : EmptyArray.INT;
mArray = EmptyArray.OBJECT;
mSize = 0;
}
diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java
index 3214b22..7da3941 100644
--- a/core/java/android/util/ArraySet.java
+++ b/core/java/android/util/ArraySet.java
@@ -245,13 +245,20 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
/**
* Create a new ArraySet with the mappings from the given ArraySet.
*/
- public ArraySet(ArraySet set) {
+ public ArraySet(ArraySet<E> set) {
this();
if (set != null) {
addAll(set);
}
}
+ /** {@hide} */
+ public ArraySet(Collection<E> set) {
+ this();
+ if (set != null) {
+ addAll(set);
+ }
+ }
/**
* Make the array map empty. All storage is released.
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index c855e57..d0e5b9e 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -62,6 +62,13 @@ public class DisplayMetrics {
public static final int DENSITY_HIGH = 240;
/**
+ * Intermediate density for screens that sit between {@link #DENSITY_HIGH} (240dpi) and
+ * {@link #DENSITY_XHIGH} (320dpi). This is not a density that applications should target,
+ * instead relying on the system to scale their {@link #DENSITY_XHIGH} assets for them.
+ */
+ public static final int DENSITY_280 = 280;
+
+ /**
* Standard quantized DPI for extra-high-density screens.
*/
public static final int DENSITY_XHIGH = 320;
diff --git a/core/java/android/util/FloatMath.java b/core/java/android/util/FloatMath.java
index 90e0636..8f488af 100644
--- a/core/java/android/util/FloatMath.java
+++ b/core/java/android/util/FloatMath.java
@@ -24,7 +24,10 @@ package android.util;
* became slower and have since been re-implemented to wrap calls to
* {@link java.lang.Math}. {@link java.lang.Math} should be used in
* preference.
+ *
+ * @deprecated Use {@link java.lang.Math} instead.
*/
+@Deprecated
public class FloatMath {
/** Prevents instantiation. */
diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java
new file mode 100644
index 0000000..e8d3947
--- /dev/null
+++ b/core/java/android/util/IntArray.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.util;
+
+import com.android.internal.util.ArrayUtils;
+
+import libcore.util.EmptyArray;
+
+/**
+ * Implements a growing array of int primitives.
+ *
+ * @hide
+ */
+public class IntArray implements Cloneable {
+ private static final int MIN_CAPACITY_INCREMENT = 12;
+
+ private int[] mValues;
+ private int mSize;
+
+ /**
+ * Creates an empty IntArray with the default initial capacity.
+ */
+ public IntArray() {
+ this(10);
+ }
+
+ /**
+ * Creates an empty IntArray with the specified initial capacity.
+ */
+ public IntArray(int initialCapacity) {
+ if (initialCapacity == 0) {
+ mValues = EmptyArray.INT;
+ } else {
+ mValues = ArrayUtils.newUnpaddedIntArray(initialCapacity);
+ }
+ mSize = 0;
+ }
+
+ /**
+ * Appends the specified value to the end of this array.
+ */
+ public void add(int value) {
+ add(mSize, value);
+ }
+
+ /**
+ * Inserts a value at the specified position in this array.
+ *
+ * @throws IndexOutOfBoundsException when index &lt; 0 || index &gt; size()
+ */
+ public void add(int index, int value) {
+ if (index < 0 || index > mSize) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ ensureCapacity(1);
+
+ if (mSize - index != 0) {
+ System.arraycopy(mValues, index, mValues, index + 1, mSize - index);
+ }
+
+ mValues[index] = value;
+ mSize++;
+ }
+
+ /**
+ * Adds the values in the specified array to this array.
+ */
+ public void addAll(IntArray values) {
+ final int count = values.mSize;
+ ensureCapacity(count);
+
+ System.arraycopy(values.mValues, 0, mValues, mSize, count);
+ mSize += count;
+ }
+
+ /**
+ * Ensures capacity to append at least <code>count</code> values.
+ */
+ private void ensureCapacity(int count) {
+ final int currentSize = mSize;
+ final int minCapacity = currentSize + count;
+ if (minCapacity >= mValues.length) {
+ final int targetCap = currentSize + (currentSize < (MIN_CAPACITY_INCREMENT / 2) ?
+ MIN_CAPACITY_INCREMENT : currentSize >> 1);
+ final int newCapacity = targetCap > minCapacity ? targetCap : minCapacity;
+ final int[] newValues = ArrayUtils.newUnpaddedIntArray(newCapacity);
+ System.arraycopy(mValues, 0, newValues, 0, currentSize);
+ mValues = newValues;
+ }
+ }
+
+ /**
+ * Removes all values from this array.
+ */
+ public void clear() {
+ mSize = 0;
+ }
+
+ @Override
+ public IntArray clone() throws CloneNotSupportedException {
+ final IntArray clone = (IntArray) super.clone();
+ clone.mValues = mValues.clone();
+ return clone;
+ }
+
+ /**
+ * Returns the value at the specified position in this array.
+ */
+ public int get(int index) {
+ if (index >= mSize) {
+ throw new ArrayIndexOutOfBoundsException(mSize, index);
+ }
+ return mValues[index];
+ }
+
+ /**
+ * Returns the index of the first occurrence of the specified value in this
+ * array, or -1 if this array does not contain the value.
+ */
+ public int indexOf(int value) {
+ final int n = mSize;
+ for (int i = 0; i < n; i++) {
+ if (mValues[i] == value) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Removes the value at the specified index from this array.
+ */
+ public void remove(int index) {
+ if (index >= mSize) {
+ throw new ArrayIndexOutOfBoundsException(mSize, index);
+ }
+ System.arraycopy(mValues, index + 1, mValues, index, mSize - index - 1);
+ mSize--;
+ }
+
+ /**
+ * Returns the number of values in this array.
+ */
+ public int size() {
+ return mSize;
+ }
+}
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index a9b3571..121485a 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -295,6 +295,11 @@ public final class Log {
return bytes;
}
+ static void wtfQuiet(int logId, String tag, String msg, boolean system) {
+ TerribleFailure what = new TerribleFailure(msg, null);
+ sWtfHandler.onTerribleFailure(tag, what, system);
+ }
+
/**
* Sets the terrible failure handler, for testing.
*
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index b18cc3b..ed2d3c6 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -19,6 +19,8 @@ package android.util;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
import android.net.SntpClient;
import android.os.SystemClock;
import android.provider.Settings;
@@ -35,10 +37,13 @@ public class NtpTrustedTime implements TrustedTime {
private static final boolean LOGD = false;
private static NtpTrustedTime sSingleton;
+ private static Context sContext;
private final String mServer;
private final long mTimeout;
+ private ConnectivityManager mCM;
+
private boolean mHasCache;
private long mCachedNtpTime;
private long mCachedNtpElapsedRealtime;
@@ -67,6 +72,7 @@ public class NtpTrustedTime implements TrustedTime {
final String server = secureServer != null ? secureServer : defaultServer;
sSingleton = new NtpTrustedTime(server, timeout);
+ sContext = context;
}
return sSingleton;
@@ -79,6 +85,20 @@ public class NtpTrustedTime implements TrustedTime {
return false;
}
+ // We can't do this at initialization time: ConnectivityService might not be running yet.
+ synchronized (this) {
+ if (mCM == null) {
+ mCM = (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+ }
+
+ final NetworkInfo ni = mCM == null ? null : mCM.getActiveNetworkInfo();
+ if (ni == null || !ni.isConnected()) {
+ if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
+ return false;
+ }
+
+
if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
final SntpClient client = new SntpClient();
if (client.requestTime(mServer, (int) mTimeout)) {
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
index 6820f77..92b19be 100644
--- a/core/java/android/util/PathParser.java
+++ b/core/java/android/util/PathParser.java
@@ -34,7 +34,11 @@ public class PathParser {
Path path = new Path();
PathDataNode[] nodes = createNodesFromPathData(pathData);
if (nodes != null) {
- PathDataNode.nodesToPath(nodes, path);
+ try {
+ PathDataNode.nodesToPath(nodes, path);
+ } catch (RuntimeException e) {
+ throw new RuntimeException("Error in parsing " + pathData, e);
+ }
return path;
}
return null;
@@ -128,7 +132,12 @@ public class PathParser {
while (end < s.length()) {
c = s.charAt(end);
- if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) {
+ // Note that 'e' or 'E' are not valid path commands, but could be
+ // used for floating point numbers' scientific notation.
+ // Therefore, when searching for next command, we should ignore 'e'
+ // and 'E'.
+ if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
+ && c != 'e' && c != 'E') {
return end;
}
end++;
@@ -142,9 +151,9 @@ public class PathParser {
private static class ExtractFloatResult {
// We need to return the position of the next separator and whether the
- // next float starts with a '-'.
+ // next float starts with a '-' or a '.'.
int mEndPosition;
- boolean mEndWithNegSign;
+ boolean mEndWithNegOrDot;
}
/**
@@ -179,8 +188,8 @@ public class PathParser {
s.substring(startPosition, endPosition));
}
- if (result.mEndWithNegSign) {
- // Keep the '-' sign with next number.
+ if (result.mEndWithNegOrDot) {
+ // Keep the '-' or '.' sign with next number.
startPosition = endPosition;
} else {
startPosition = endPosition + 1;
@@ -188,8 +197,7 @@ public class PathParser {
}
return Arrays.copyOf(results, count);
} catch (NumberFormatException e) {
- Log.e(LOGTAG, "error in parsing \"" + s + "\"");
- throw e;
+ throw new RuntimeException("error in parsing \"" + s + "\"", e);
}
}
@@ -201,11 +209,15 @@ public class PathParser {
* the starting position of next number, whether it is ending with a '-'.
*/
private static void extract(String s, int start, ExtractFloatResult result) {
- // Now looking for ' ', ',' or '-' from the start.
+ // Now looking for ' ', ',', '.' or '-' from the start.
int currentIndex = start;
boolean foundSeparator = false;
- result.mEndWithNegSign = false;
+ result.mEndWithNegOrDot = false;
+ boolean secondDot = false;
+ boolean isExponential = false;
for (; currentIndex < s.length(); currentIndex++) {
+ boolean isPrevExponential = isExponential;
+ isExponential = false;
char currentChar = s.charAt(currentIndex);
switch (currentChar) {
case ' ':
@@ -213,11 +225,25 @@ public class PathParser {
foundSeparator = true;
break;
case '-':
- if (currentIndex != start) {
+ // The negative sign following a 'e' or 'E' is not a separator.
+ if (currentIndex != start && !isPrevExponential) {
+ foundSeparator = true;
+ result.mEndWithNegOrDot = true;
+ }
+ break;
+ case '.':
+ if (!secondDot) {
+ secondDot = true;
+ } else {
+ // This is the second dot, and it is considered as a separator.
foundSeparator = true;
- result.mEndWithNegSign = true;
+ result.mEndWithNegOrDot = true;
}
break;
+ case 'e':
+ case 'E':
+ isExponential = true;
+ break;
}
if (foundSeparator) {
break;
@@ -254,7 +280,7 @@ public class PathParser {
* @param path The target Path object.
*/
public static void nodesToPath(PathDataNode[] node, Path path) {
- float[] current = new float[4];
+ float[] current = new float[6];
char previousCommand = 'm';
for (int i = 0; i < node.length; i++) {
addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
@@ -287,6 +313,8 @@ public class PathParser {
float currentY = current[1];
float ctrlPointX = current[2];
float ctrlPointY = current[3];
+ float currentSegmentStartX = current[4];
+ float currentSegmentStartY = current[5];
float reflectiveCtrlPointX;
float reflectiveCtrlPointY;
@@ -294,7 +322,15 @@ public class PathParser {
case 'z':
case 'Z':
path.close();
- return;
+ // Path is closed here, but we need to move the pen to the
+ // closed position. So we cache the segment's starting position,
+ // and restore it here.
+ currentX = currentSegmentStartX;
+ currentY = currentSegmentStartY;
+ ctrlPointX = currentSegmentStartX;
+ ctrlPointY = currentSegmentStartY;
+ path.moveTo(currentX, currentY);
+ break;
case 'm':
case 'M':
case 'l':
@@ -324,17 +360,22 @@ public class PathParser {
incr = 7;
break;
}
+
for (int k = 0; k < val.length; k += incr) {
switch (cmd) {
case 'm': // moveto - Start a new sub-path (relative)
path.rMoveTo(val[k + 0], val[k + 1]);
currentX += val[k + 0];
currentY += val[k + 1];
+ currentSegmentStartX = currentX;
+ currentSegmentStartY = currentY;
break;
case 'M': // moveto - Start a new sub-path
path.moveTo(val[k + 0], val[k + 1]);
currentX = val[k + 0];
currentY = val[k + 1];
+ currentSegmentStartX = currentX;
+ currentSegmentStartY = currentY;
break;
case 'l': // lineto - Draw a line from the current point (relative)
path.rLineTo(val[k + 0], val[k + 1]);
@@ -346,10 +387,6 @@ public class PathParser {
currentX = val[k + 0];
currentY = val[k + 1];
break;
- case 'z': // closepath - Close the current subpath
- case 'Z': // closepath - Close the current subpath
- path.close();
- break;
case 'h': // horizontal lineto - Draws a horizontal line (relative)
path.rLineTo(val[k + 0], 0);
currentX += val[k + 0];
@@ -500,6 +537,8 @@ public class PathParser {
current[1] = currentY;
current[2] = ctrlPointX;
current[3] = ctrlPointY;
+ current[4] = currentSegmentStartX;
+ current[5] = currentSegmentStartY;
}
private static void drawArc(Path p,
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index e92b846..58a2703 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -83,6 +83,13 @@ public final class Slog {
}
/**
+ * Like {@link #wtf(String, String)}, but does not output anything to the log.
+ */
+ public static void wtfQuiet(String tag, String msg) {
+ Log.wtfQuiet(Log.LOG_ID_SYSTEM, tag, msg, true);
+ }
+
+ /**
* Like {@link Log#wtfStack(String, String)}, but will never cause the caller to crash, and
* will always be handled asynchronously. Primarily for use by coding running within
* the system process.
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
index 931fb81..74d4245 100644
--- a/core/java/android/util/TypedValue.java
+++ b/core/java/android/util/TypedValue.java
@@ -139,6 +139,17 @@ public class TypedValue {
/* ------------------------------------------------------------ */
/**
+ * {@link #TYPE_NULL} data indicating the value was not specified.
+ */
+ public static final int DATA_NULL_UNDEFINED = 0;
+ /**
+ * {@link #TYPE_NULL} data indicating the value was explicitly set to null.
+ */
+ public static final int DATA_NULL_EMPTY = 1;
+
+ /* ------------------------------------------------------------ */
+
+ /**
* If {@link #density} is equal to this value, then the density should be
* treated as the system's default density value: {@link DisplayMetrics#DENSITY_DEFAULT}.
*/
@@ -301,6 +312,18 @@ public class TypedValue {
}
/**
+ * Return the complex unit type for this value. For example, a dimen type
+ * with value 12sp will return {@link #COMPLEX_UNIT_SP}. Only use for values
+ * whose type is {@link #TYPE_DIMENSION}.
+ *
+ * @return The complex unit type.
+ */
+ public int getComplexUnit()
+ {
+ return COMPLEX_UNIT_MASK & (data>>TypedValue.COMPLEX_UNIT_SHIFT);
+ }
+
+ /**
* Converts an unpacked complex data value holding a dimension to its final floating
* point value. The two parameters <var>unit</var> and <var>value</var>
* are as in {@link #TYPE_DIMENSION}.
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 1cadf69..68ad782 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -636,95 +636,6 @@ final class AccessibilityInteractionController {
}
}
- public void computeClickPointInScreenClientThread(long accessibilityNodeId,
- Region interactiveRegion, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
- long interrogatingTid, MagnificationSpec spec) {
- Message message = mHandler.obtainMessage();
- message.what = PrivateHandler.MSG_COMPUTE_CLICK_POINT_IN_SCREEN;
-
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
- args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
- args.argi3 = interactionId;
- args.arg1 = callback;
- args.arg2 = spec;
- args.arg3 = interactiveRegion;
-
- message.obj = args;
-
- // If the interrogation is performed by the same thread as the main UI
- // thread in this process, set the message as a static reference so
- // after this call completes the same thread but in the interrogating
- // client can handle the message to generate the result.
- if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
- AccessibilityInteractionClient.getInstanceForThread(
- interrogatingTid).setSameThreadMessage(message);
- } else {
- mHandler.sendMessage(message);
- }
- }
-
- private void computeClickPointInScreenUiThread(Message message) {
- SomeArgs args = (SomeArgs) message.obj;
- final int accessibilityViewId = args.argi1;
- final int virtualDescendantId = args.argi2;
- final int interactionId = args.argi3;
- final IAccessibilityInteractionConnectionCallback callback =
- (IAccessibilityInteractionConnectionCallback) args.arg1;
- final MagnificationSpec spec = (MagnificationSpec) args.arg2;
- final Region interactiveRegion = (Region) args.arg3;
- args.recycle();
-
- boolean succeeded = false;
- Point point = mTempPoint;
- try {
- if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
- return;
- }
- View target = null;
- if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
- target = findViewByAccessibilityId(accessibilityViewId);
- } else {
- target = mViewRootImpl.mView;
- }
- if (target != null && isShown(target)) {
- AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
- if (provider != null) {
- // For virtual views just use the center of the bounds in screen.
- AccessibilityNodeInfo node = null;
- if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
- node = provider.createAccessibilityNodeInfo(virtualDescendantId);
- } else {
- node = provider.createAccessibilityNodeInfo(
- AccessibilityNodeProvider.HOST_VIEW_ID);
- }
- if (node != null) {
- succeeded = true;
- Rect boundsInScreen = mTempRect;
- node.getBoundsInScreen(boundsInScreen);
- point.set(boundsInScreen.centerX(), boundsInScreen.centerY());
- }
- } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
- // For a real view, ask the view to compute the click point.
- succeeded = target.computeClickPointInScreenForAccessibility(
- interactiveRegion, point);
- }
- }
- } finally {
- try {
- Point result = null;
- if (succeeded) {
- applyAppScaleAndMagnificationSpecIfNeeded(point, spec);
- result = point;
- }
- callback.setComputeClickPointInScreenActionResult(result, interactionId);
- } catch (RemoteException re) {
- /* ignore - the other side will time out */
- }
- }
- }
-
private View findViewByAccessibilityId(int accessibilityId) {
View root = mViewRootImpl.mView;
if (root == null) {
@@ -1109,15 +1020,17 @@ final class AccessibilityInteractionController {
|| accessibilityViewId == providerHost.getAccessibilityViewId()) {
final AccessibilityNodeInfo parent;
if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
- parent = provider.createAccessibilityNodeInfo(
- virtualDescendantId);
+ parent = provider.createAccessibilityNodeInfo(virtualDescendantId);
} else {
- parent= provider.createAccessibilityNodeInfo(
+ parent = provider.createAccessibilityNodeInfo(
AccessibilityNodeProvider.HOST_VIEW_ID);
}
- if (parent != null) {
- outInfos.add(parent);
+ if (parent == null) {
+ // Couldn't obtain the parent, which means we have a
+ // disconnected sub-tree. Abort prefetch immediately.
+ return;
}
+ outInfos.add(parent);
parentNodeId = parent.getParentNodeId();
accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
parentNodeId);
@@ -1199,7 +1112,6 @@ final class AccessibilityInteractionController {
private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;
private final static int MSG_FIND_FOCUS = 5;
private final static int MSG_FOCUS_SEARCH = 6;
- private final static int MSG_COMPUTE_CLICK_POINT_IN_SCREEN = 7;
public PrivateHandler(Looper looper) {
super(looper);
@@ -1221,8 +1133,6 @@ final class AccessibilityInteractionController {
return "MSG_FIND_FOCUS";
case MSG_FOCUS_SEARCH:
return "MSG_FOCUS_SEARCH";
- case MSG_COMPUTE_CLICK_POINT_IN_SCREEN:
- return "MSG_COMPUTE_CLICK_POINT_IN_SCREEN";
default:
throw new IllegalArgumentException("Unknown message type: " + type);
}
@@ -1250,9 +1160,6 @@ final class AccessibilityInteractionController {
case MSG_FOCUS_SEARCH: {
focusSearchUiThread(message);
} break;
- case MSG_COMPUTE_CLICK_POINT_IN_SCREEN: {
- computeClickPointInScreenUiThread(message);
- } break;
default:
throw new IllegalArgumentException("Unknown message type: " + type);
}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 56a05fe..9feb681 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -59,6 +59,11 @@ public final class DisplayInfo implements Parcelable {
public String name;
/**
+ * Unique identifier for the display. Shouldn't be displayed to the user.
+ */
+ public String uniqueId;
+
+ /**
* The width of the portion of the display that is available to applications, in pixels.
* Represents the size of the display minus any system decorations.
*/
@@ -257,7 +262,7 @@ public final class DisplayInfo implements Parcelable {
&& flags == other.flags
&& type == other.type
&& Objects.equal(address, other.address)
- && Objects.equal(name, other.name)
+ && Objects.equal(uniqueId, other.uniqueId)
&& appWidth == other.appWidth
&& appHeight == other.appHeight
&& smallestNominalAppWidth == other.smallestNominalAppWidth
@@ -293,6 +298,7 @@ public final class DisplayInfo implements Parcelable {
type = other.type;
address = other.address;
name = other.name;
+ uniqueId = other.uniqueId;
appWidth = other.appWidth;
appHeight = other.appHeight;
smallestNominalAppWidth = other.smallestNominalAppWidth;
@@ -348,6 +354,7 @@ public final class DisplayInfo implements Parcelable {
state = source.readInt();
ownerUid = source.readInt();
ownerPackageName = source.readString();
+ uniqueId = source.readString();
}
@Override
@@ -380,6 +387,7 @@ public final class DisplayInfo implements Parcelable {
dest.writeInt(state);
dest.writeInt(ownerUid);
dest.writeString(ownerPackageName);
+ dest.writeString(uniqueId);
}
@Override
@@ -445,6 +453,8 @@ public final class DisplayInfo implements Parcelable {
StringBuilder sb = new StringBuilder();
sb.append("DisplayInfo{\"");
sb.append(name);
+ sb.append("\", uniqueId \"");
+ sb.append(uniqueId);
sb.append("\", app ");
sb.append(appWidth);
sb.append(" x ");
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 1b57c24..60a489b 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -216,7 +216,7 @@ class GLES20Canvas extends HardwareCanvas {
///////////////////////////////////////////////////////////////////////////
@Override
- public int callDrawGLFunction(long drawGLFunction) {
+ public int callDrawGLFunction2(long drawGLFunction) {
return nCallDrawGLFunction(mRenderer, drawGLFunction);
}
@@ -242,7 +242,7 @@ class GLES20Canvas extends HardwareCanvas {
void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) {
layer.setLayerPaint(paint);
- nDrawLayer(mRenderer, layer.getLayer(), x, y);
+ nDrawLayer(mRenderer, layer.getLayerHandle(), x, y);
}
private static native void nDrawLayer(long renderer, long layer, float x, float y);
@@ -558,7 +558,7 @@ class GLES20Canvas extends HardwareCanvas {
Bitmap bitmap = patch.getBitmap();
throwIfCannotDraw(bitmap);
final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, patch.mNativeChunk,
+ nDrawPatch(mRenderer, bitmap.mNativeBitmap, patch.mNativeChunk,
dst.left, dst.top, dst.right, dst.bottom, nativePaint);
}
@@ -567,32 +567,31 @@ class GLES20Canvas extends HardwareCanvas {
Bitmap bitmap = patch.getBitmap();
throwIfCannotDraw(bitmap);
final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, patch.mNativeChunk,
+ nDrawPatch(mRenderer, bitmap.mNativeBitmap, patch.mNativeChunk,
dst.left, dst.top, dst.right, dst.bottom, nativePaint);
}
- private static native void nDrawPatch(long renderer, long bitmap, byte[] buffer, long chunk,
+ private static native void nDrawPatch(long renderer, long bitmap, long chunk,
float left, float top, float right, float bottom, long paint);
@Override
public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
throwIfCannotDraw(bitmap);
final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint);
+ nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, nativePaint);
}
- private static native void nDrawBitmap(long renderer, long bitmap, byte[] buffer,
- float left, float top, long paint);
+ private static native void nDrawBitmap(long renderer, long bitmap, float left,
+ float top, long paint);
@Override
public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
throwIfCannotDraw(bitmap);
final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer,
- matrix.native_instance, nativePaint);
+ nDrawBitmap(mRenderer, bitmap.mNativeBitmap, matrix.native_instance, nativePaint);
}
- private static native void nDrawBitmap(long renderer, long bitmap, byte[] buffer,
+ private static native void nDrawBitmap(long renderer, long bitmap,
long matrix, long paint);
@Override
@@ -612,7 +611,7 @@ class GLES20Canvas extends HardwareCanvas {
bottom = src.bottom;
}
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom,
+ nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom,
dst.left, dst.top, dst.right, dst.bottom, nativePaint);
}
@@ -633,11 +632,11 @@ class GLES20Canvas extends HardwareCanvas {
bottom = src.bottom;
}
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom,
+ nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom,
dst.left, dst.top, dst.right, dst.bottom, nativePaint);
}
- private static native void nDrawBitmap(long renderer, long bitmap, byte[] buffer,
+ private static native void nDrawBitmap(long renderer, long bitmap,
float srcLeft, float srcTop, float srcRight, float srcBottom,
float left, float top, float right, float bottom, long paint);
@@ -698,11 +697,11 @@ class GLES20Canvas extends HardwareCanvas {
}
final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, meshWidth, meshHeight,
+ nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, meshWidth, meshHeight,
verts, vertOffset, colors, colorOffset, nativePaint);
}
- private static native void nDrawBitmapMesh(long renderer, long bitmap, byte[] buffer,
+ private static native void nDrawBitmapMesh(long renderer, long bitmap,
int meshWidth, int meshHeight, float[] verts, int vertOffset,
int[] colors, int colorOffset, long paint);
@@ -885,6 +884,9 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
+ if ((start | end | (end - start) | (text.length() - end)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
if (text instanceof String || text instanceof SpannedString ||
text instanceof SpannableString) {
nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags,
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index e3eee71..18accb8 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -105,10 +105,7 @@ public abstract class HardwareCanvas extends Canvas {
*
* @hide
*/
- public int callDrawGLFunction(long drawGLFunction) {
- // Noop - this is done in the display list recorder subclass
- return RenderNode.STATUS_DONE;
- }
+ public abstract int callDrawGLFunction2(long drawGLFunction);
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 0c2e944..a130bda 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -126,8 +126,8 @@ final class HardwareLayer {
mRenderer.detachSurfaceTexture(mFinalizer.get());
}
- public long getLayer() {
- return nGetLayer(mFinalizer.get());
+ public long getLayerHandle() {
+ return mFinalizer.get();
}
public void setSurfaceTexture(SurfaceTexture surface) {
@@ -153,6 +153,5 @@ final class HardwareLayer {
private static native void nUpdateRenderLayer(long layerUpdater, long displayList,
int left, int top, int right, int bottom);
- private static native long nGetLayer(long layerUpdater);
private static native int nGetTexName(long layerUpdater);
}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 904e33f..c5c3f83 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -235,7 +235,7 @@ public abstract class HardwareRenderer {
* or not the surface used by the HardwareRenderer will be changing. It
* Suspends any rendering into the surface, but will not do any destruction
*/
- abstract void pauseSurface(Surface surface);
+ abstract boolean pauseSurface(Surface surface);
/**
* Destroys all hardware rendering resources associated with the specified
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 3e7aae0..9fc80fc 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -85,4 +85,9 @@ oneway interface IWindow {
* is done.
*/
void doneAnimating();
+
+ /**
+ * Called for non-application windows when the enter animation has completed.
+ */
+ void dispatchWindowShown();
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 6aa86c7..7b20e72 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -96,6 +96,7 @@ interface IWindowManager
void overridePendingAppTransitionAspectScaledThumb(in Bitmap srcThumb, int startX,
int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback,
boolean scaleUp);
+ void overridePendingAppTransitionInPlace(String packageName, int anim);
void executeAppTransition();
void setAppStartingWindow(IBinder token, String pkg, int theme,
in CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 037ed28..7b13e84 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -36,15 +36,16 @@ import android.view.Surface;
*/
interface IWindowSession {
int add(IWindow window, int seq, in WindowManager.LayoutParams attrs,
- in int viewVisibility, out Rect outContentInsets,
+ in int viewVisibility, out Rect outContentInsets, out Rect outStableInsets,
out InputChannel outInputChannel);
int addToDisplay(IWindow window, int seq, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, out Rect outContentInsets,
- out InputChannel outInputChannel);
+ out Rect outStableInsets, out InputChannel outInputChannel);
int addWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,
- in int viewVisibility, out Rect outContentInsets);
+ in int viewVisibility, out Rect outContentInsets, out Rect outStableInsets);
int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,
- in int viewVisibility, in int layerStackId, out Rect outContentInsets);
+ in int viewVisibility, in int layerStackId, out Rect outContentInsets,
+ out Rect outStableInsets);
void remove(IWindow window);
/**
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index 1ecdf30..e2ad3ad 100644
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -196,6 +196,13 @@ public abstract class InputEvent implements Parcelable {
public abstract long getEventTimeNano();
/**
+ * Marks the input event as being canceled.
+ *
+ * @hide
+ */
+ public abstract void cancel();
+
+ /**
* Gets the unique sequence number of this event.
* Every input event that is created or received by a process has a
* unique sequence number. Moreover, a new sequence number is obtained
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 0701b53..243a0fc 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -2304,6 +2304,16 @@ public class KeyEvent extends InputEvent implements Parcelable {
}
/**
+ * Set {@link #FLAG_CANCELED} flag for the key event.
+ *
+ * @hide
+ */
+ @Override
+ public final void cancel() {
+ mFlags |= FLAG_CANCELED;
+ }
+
+ /**
* Call this during {@link Callback#onKeyDown} to have the system track
* the key through its final up (possibly including a long press). Note
* that only one key can be tracked at a time -- if another key down
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index ae39b7a..5e45c8f 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -402,6 +402,23 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int FLAG_TAINTED = 0x80000000;
/**
+ * Private flag indicating that this event was synthesized by the system and
+ * should be delivered to the accessibility focused view first. When being
+ * dispatched such an event is not handled by predecessors of the accessibility
+ * focused view and after the event reaches that view the flag is cleared and
+ * normal event dispatch is performed. This ensures that the platform can click
+ * on any view that has accessibility focus which is semantically equivalent to
+ * asking the view to perform a click accessibility action but more generic as
+ * views not implementing click action correctly can still be activated.
+ *
+ * @hide
+ * @see #isTargetAccessibilityFocus()
+ * @see #setTargetAccessibilityFocus(boolean)
+ */
+ public static final int FLAG_TARGET_ACCESSIBILITY_FOCUS = 0x40000000;
+
+
+ /**
* Flag indicating the motion event intersected the top edge of the screen.
*/
public static final int EDGE_TOP = 0x00000001;
@@ -1766,6 +1783,20 @@ public final class MotionEvent extends InputEvent implements Parcelable {
nativeSetFlags(mNativePtr, tainted ? flags | FLAG_TAINTED : flags & ~FLAG_TAINTED);
}
+ /** @hide */
+ public final boolean isTargetAccessibilityFocus() {
+ final int flags = getFlags();
+ return (flags & FLAG_TARGET_ACCESSIBILITY_FOCUS) != 0;
+ }
+
+ /** @hide */
+ public final void setTargetAccessibilityFocus(boolean targetsFocus) {
+ final int flags = getFlags();
+ nativeSetFlags(mNativePtr, targetsFocus
+ ? flags | FLAG_TARGET_ACCESSIBILITY_FOCUS
+ : flags & ~FLAG_TARGET_ACCESSIBILITY_FOCUS);
+ }
+
/**
* Returns the time (in ms) when the user originally pressed down to start
* a stream of position events.
@@ -3168,6 +3199,12 @@ public final class MotionEvent extends InputEvent implements Parcelable {
return ev;
}
+ /** @hide */
+ @Override
+ public final void cancel() {
+ setAction(ACTION_CANCEL);
+ }
+
public void writeToParcel(Parcel out, int flags) {
out.writeInt(PARCEL_TOKEN_MOTION_EVENT);
nativeWriteToParcel(mNativePtr, out);
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index debf45d..7b35a3b 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -148,6 +148,10 @@ public class RenderNodeAnimator extends Animator {
if (mState != STATE_PREPARE) {
throw new IllegalStateException("Animator has already started, cannot change it now!");
}
+ if (mNativePtr == null) {
+ throw new IllegalStateException("Animator's target has been destroyed "
+ + "(trying to modify an animation after activity destroy?)");
+ }
}
static boolean isNativeInterpolator(TimeInterpolator interpolator) {
@@ -180,7 +184,10 @@ public class RenderNodeAnimator extends Animator {
mState = STATE_DELAYED;
applyInterpolator();
- if (mStartDelay <= 0 || !mUiThreadHandlesDelay) {
+ if (mNativePtr == null) {
+ // It's dead, immediately cancel
+ cancel();
+ } else if (mStartDelay <= 0 || !mUiThreadHandlesDelay) {
nSetStartDelay(mNativePtr.get(), mStartDelay);
doStart();
} else {
@@ -189,9 +196,6 @@ public class RenderNodeAnimator extends Animator {
}
private void doStart() {
- mState = STATE_RUNNING;
- nStart(mNativePtr.get(), this);
-
// Alpha is a special snowflake that has the canonical value stored
// in mTransformationInfo instead of in RenderNode, so we need to update
// it with the final value here.
@@ -201,7 +205,7 @@ public class RenderNodeAnimator extends Animator {
mViewTarget.mTransformationInfo.mAlpha = mFinalValue;
}
- notifyStartListeners();
+ moveToRunningState();
if (mViewTarget != null) {
// Kick off a frame to start the process
@@ -209,6 +213,14 @@ public class RenderNodeAnimator extends Animator {
}
}
+ private void moveToRunningState() {
+ mState = STATE_RUNNING;
+ if (mNativePtr != null) {
+ nStart(mNativePtr.get());
+ }
+ notifyStartListeners();
+ }
+
private void notifyStartListeners() {
final ArrayList<AnimatorListener> listeners = cloneListeners();
final int numListeners = listeners == null ? 0 : listeners.size();
@@ -219,12 +231,11 @@ public class RenderNodeAnimator extends Animator {
@Override
public void cancel() {
- if (mState != STATE_FINISHED) {
+ if (mState != STATE_PREPARE && mState != STATE_FINISHED) {
if (mState == STATE_DELAYED) {
getHelper().removeDelayedAnimation(this);
- notifyStartListeners();
+ moveToRunningState();
}
- nEnd(mNativePtr.get());
final ArrayList<AnimatorListener> listeners = cloneListeners();
final int numListeners = listeners == null ? 0 : listeners.size();
@@ -232,17 +243,27 @@ public class RenderNodeAnimator extends Animator {
listeners.get(i).onAnimationCancel(this);
}
- if (mViewTarget != null) {
- // Kick off a frame to flush the state change
- mViewTarget.invalidateViewProperty(true, false);
- }
+ end();
}
}
@Override
public void end() {
if (mState != STATE_FINISHED) {
- nEnd(mNativePtr.get());
+ if (mState < STATE_RUNNING) {
+ getHelper().removeDelayedAnimation(this);
+ doStart();
+ }
+ if (mNativePtr != null) {
+ nEnd(mNativePtr.get());
+ if (mViewTarget != null) {
+ // Kick off a frame to flush the state change
+ mViewTarget.invalidateViewProperty(true, false);
+ }
+ } else {
+ // It's already dead, jump to onFinish
+ onFinished();
+ }
}
}
@@ -270,9 +291,11 @@ public class RenderNodeAnimator extends Animator {
}
private void setTarget(RenderNode node) {
+ checkMutable();
if (mTarget != null) {
throw new IllegalStateException("Target already set!");
}
+ nSetListener(mNativePtr.get(), this);
mTarget = node;
mTarget.addAnimator(this);
}
@@ -335,6 +358,12 @@ public class RenderNodeAnimator extends Animator {
}
protected void onFinished() {
+ if (mState == STATE_PREPARE) {
+ // Unlikely but possible, the native side has been destroyed
+ // before we have started.
+ releaseNativePtr();
+ return;
+ }
if (mState == STATE_DELAYED) {
getHelper().removeDelayedAnimation(this);
notifyStartListeners();
@@ -350,8 +379,14 @@ public class RenderNodeAnimator extends Animator {
// Release the native object, as it has a global reference to us. This
// breaks the cyclic reference chain, and allows this object to be
// GC'd
- mNativePtr.release();
- mNativePtr = null;
+ releaseNativePtr();
+ }
+
+ private void releaseNativePtr() {
+ if (mNativePtr != null) {
+ mNativePtr.release();
+ mNativePtr = null;
+ }
}
@SuppressWarnings("unchecked")
@@ -473,7 +508,8 @@ public class RenderNodeAnimator extends Animator {
private static native void nSetStartDelay(long nativePtr, long startDelay);
private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
private static native void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync);
+ private static native void nSetListener(long animPtr, RenderNodeAnimator listener);
- private static native void nStart(long animPtr, RenderNodeAnimator finishListener);
+ private static native void nStart(long animPtr);
private static native void nEnd(long animPtr);
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 3770b8a..33ce517 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -53,6 +53,9 @@ public class Surface implements Parcelable {
private static native void nativeAllocateBuffers(long nativeObject);
+ private static native int nativeGetWidth(long nativeObject);
+ private static native int nativeGetHeight(long nativeObject);
+
public static final Parcelable.Creator<Surface> CREATOR =
new Parcelable.Creator<Surface>() {
@Override
@@ -87,6 +90,8 @@ public class Surface implements Parcelable {
// non compatibility mode.
private Matrix mCompatibleMatrix;
+ private HwuiContext mHwuiContext;
+
/** @hide */
@IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
@Retention(RetentionPolicy.SOURCE)
@@ -171,6 +176,10 @@ public class Surface implements Parcelable {
nativeRelease(mNativeObject);
setNativeObjectLocked(0);
}
+ if (mHwuiContext != null) {
+ mHwuiContext.destroy();
+ mHwuiContext = null;
+ }
}
}
@@ -264,27 +273,66 @@ public class Surface implements Parcelable {
* @param canvas The canvas previously obtained from {@link #lockCanvas}.
*/
public void unlockCanvasAndPost(Canvas canvas) {
+ synchronized (mLock) {
+ checkNotReleasedLocked();
+
+ if (mHwuiContext != null) {
+ mHwuiContext.unlockAndPost(canvas);
+ } else {
+ unlockSwCanvasAndPost(canvas);
+ }
+ }
+ }
+
+ private void unlockSwCanvasAndPost(Canvas canvas) {
if (canvas != mCanvas) {
throw new IllegalArgumentException("canvas object must be the same instance that "
+ "was previously returned by lockCanvas");
}
+ if (mNativeObject != mLockedObject) {
+ Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
+ Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
+ Long.toHexString(mLockedObject) +")");
+ }
+ if (mLockedObject == 0) {
+ throw new IllegalStateException("Surface was not locked");
+ }
+ try {
+ nativeUnlockCanvasAndPost(mLockedObject, canvas);
+ } finally {
+ nativeRelease(mLockedObject);
+ mLockedObject = 0;
+ }
+ }
+ /**
+ * Gets a {@link Canvas} for drawing into this surface.
+ *
+ * After drawing into the provided {@link Canvas}, the caller must
+ * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+ *
+ * Unlike {@link #lockCanvas(Rect)} this will return a hardware-accelerated
+ * canvas. See the <a href="{@docRoot}guide/topics/graphics/hardware-accel.html#unsupported">
+ * unsupported drawing operations</a> for a list of what is and isn't
+ * supported in a hardware-accelerated canvas. It is also required to
+ * fully cover the surface every time {@link #lockHardwareCanvas()} is
+ * called as the buffer is not preserved between frames. Partial updates
+ * are not supported.
+ *
+ * @return A canvas for drawing into the surface.
+ *
+ * @throws IllegalStateException If the canvas cannot be locked.
+ * @hide
+ */
+ public Canvas lockHardwareCanvas() {
synchronized (mLock) {
checkNotReleasedLocked();
- if (mNativeObject != mLockedObject) {
- Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
- Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
- Long.toHexString(mLockedObject) +")");
- }
- if (mLockedObject == 0) {
- throw new IllegalStateException("Surface was not locked");
- }
- try {
- nativeUnlockCanvasAndPost(mLockedObject, canvas);
- } finally {
- nativeRelease(mLockedObject);
- mLockedObject = 0;
+ if (mHwuiContext == null) {
+ mHwuiContext = new HwuiContext();
}
+ return mHwuiContext.lockCanvas(
+ nativeGetWidth(mNativeObject),
+ nativeGetHeight(mNativeObject));
}
}
@@ -415,6 +463,9 @@ public class Surface implements Parcelable {
}
mNativeObject = ptr;
mGenerationId += 1;
+ if (mHwuiContext != null) {
+ mHwuiContext.updateSurface();
+ }
}
}
@@ -518,4 +569,50 @@ public class Surface implements Parcelable {
mOrigMatrix.set(m);
}
}
+
+ private final class HwuiContext {
+ private final RenderNode mRenderNode;
+ private long mHwuiRenderer;
+ private HardwareCanvas mCanvas;
+
+ HwuiContext() {
+ mRenderNode = RenderNode.create("HwuiCanvas", null);
+ mRenderNode.setClipToBounds(false);
+ mHwuiRenderer = nHwuiCreate(mRenderNode.mNativeRenderNode, mNativeObject);
+ }
+
+ Canvas lockCanvas(int width, int height) {
+ if (mCanvas != null) {
+ throw new IllegalStateException("Surface was already locked!");
+ }
+ mCanvas = mRenderNode.start(width, height);
+ return mCanvas;
+ }
+
+ void unlockAndPost(Canvas canvas) {
+ if (canvas != mCanvas) {
+ throw new IllegalArgumentException("canvas object must be the same instance that "
+ + "was previously returned by lockCanvas");
+ }
+ mRenderNode.end(mCanvas);
+ mCanvas = null;
+ nHwuiDraw(mHwuiRenderer);
+ }
+
+ void updateSurface() {
+ nHwuiSetSurface(mHwuiRenderer, mNativeObject);
+ }
+
+ void destroy() {
+ if (mHwuiRenderer != 0) {
+ nHwuiDestroy(mHwuiRenderer);
+ mHwuiRenderer = 0;
+ }
+ }
+ }
+
+ private static native long nHwuiCreate(long rootNode, long surface);
+ private static native void nHwuiSetSurface(long renderer, long surface);
+ private static native void nHwuiDraw(long renderer);
+ private static native void nHwuiDestroy(long renderer);
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index afc804c..49be57d 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -496,7 +496,8 @@ public class SurfaceView extends View {
mLayout.type = mWindowType;
mLayout.gravity = Gravity.START|Gravity.TOP;
mSession.addToDisplayWithoutInputChannel(mWindow, mWindow.mSeq, mLayout,
- mVisible ? VISIBLE : GONE, display.getDisplayId(), mContentInsets);
+ mVisible ? VISIBLE : GONE, display.getDisplayId(), mContentInsets,
+ mStableInsets);
}
boolean realSizeChanged;
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 1e46517..ad4a048 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -37,6 +37,7 @@ import android.view.View.AttachInfo;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashSet;
/**
@@ -66,6 +67,8 @@ public class ThreadedRenderer extends HardwareRenderer {
private static final int SYNC_OK = 0;
// Needs a ViewRoot invalidate
private static final int SYNC_INVALIDATE_REQUIRED = 1 << 0;
+ // Spoiler: the reward is GPU-accelerated drawing, better find that Surface!
+ private static final int SYNC_LOST_SURFACE_REWARD_IF_FOUND = 1 << 1;
private static final String[] VISUALIZERS = {
PROFILE_PROPERTY_VISUALIZE_BARS,
@@ -153,8 +156,8 @@ public class ThreadedRenderer extends HardwareRenderer {
}
@Override
- void pauseSurface(Surface surface) {
- nPauseSurface(mNativeProxy, surface);
+ boolean pauseSurface(Surface surface) {
+ return nPauseSurface(mNativeProxy, surface);
}
@Override
@@ -191,7 +194,8 @@ public class ThreadedRenderer extends HardwareRenderer {
final float lightX = width / 2.0f;
mWidth = width;
mHeight = height;
- if (surfaceInsets != null && !surfaceInsets.isEmpty()) {
+ if (surfaceInsets != null && (surfaceInsets.left != 0 || surfaceInsets.right != 0
+ || surfaceInsets.top != 0 || surfaceInsets.bottom != 0)) {
mHasInsets = true;
mInsetLeft = surfaceInsets.left;
mInsetTop = surfaceInsets.top;
@@ -255,6 +259,9 @@ public class ThreadedRenderer extends HardwareRenderer {
mProfilingEnabled = wantProfiling;
changed = true;
}
+ if (changed) {
+ invalidateRoot();
+ }
return changed;
}
@@ -268,7 +275,7 @@ public class ThreadedRenderer extends HardwareRenderer {
}
private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
updateViewTreeDisplayList(view);
if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
@@ -332,6 +339,13 @@ public class ThreadedRenderer extends HardwareRenderer {
int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,
recordDuration, view.getResources().getDisplayMetrics().density);
+ if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
+ setEnabled(false);
+ attachInfo.mViewRootImpl.mSurface.release();
+ // Invalidate since we failed to draw. This should fetch a Surface
+ // if it is still needed or do nothing if we are no longer drawing
+ attachInfo.mViewRootImpl.invalidate();
+ }
if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) {
attachInfo.mViewRootImpl.invalidate();
}
@@ -452,11 +466,13 @@ public class ThreadedRenderer extends HardwareRenderer {
final LongSparseArray<Drawable.ConstantState> drawables = resources.getPreloadedDrawables();
final int count = drawables.size();
+ ArrayList<Bitmap> tmpList = new ArrayList<Bitmap>();
for (int i = 0; i < count; i++) {
- final Bitmap bitmap = drawables.valueAt(i).getBitmap();
- if (bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888) {
- preloadedPointers.add(bitmap.mNativeBitmap);
+ drawables.valueAt(i).addAtlasableBitmaps(tmpList);
+ for (int j = 0; j < tmpList.size(); j++) {
+ preloadedPointers.add(tmpList.get(j).mNativeBitmap);
}
+ tmpList.clear();
}
for (int i = 0; i < map.length; i += 4) {
@@ -481,7 +497,7 @@ 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 boolean nPauseSurface(long nativeProxy, Surface window);
private static native void nSetup(long nativeProxy, int width, int height,
float lightX, float lightY, float lightZ, float lightRadius,
int ambientShadowAlpha, int spotShadowAlpha);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1ecc8d9..3c05872 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -56,6 +56,7 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.FloatProperty;
@@ -2400,12 +2401,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
static final int PFLAG3_NESTED_SCROLLING_ENABLED = 0x80;
- /**
- * Flag indicating that outline was invalidated and should be rebuilt the next time
- * the DisplayList is updated.
- */
- static final int PFLAG3_OUTLINE_INVALID = 0x100;
-
/* End of masks for mPrivateFlags3 */
static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED;
@@ -3109,6 +3104,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private MatchLabelForPredicate mMatchLabelForPredicate;
/**
+ * Specifies a view before which this one is visited in accessibility traversal.
+ */
+ private int mAccessibilityTraversalBeforeId = NO_ID;
+
+ /**
+ * Specifies a view after which this one is visited in accessibility traversal.
+ */
+ private int mAccessibilityTraversalAfterId = NO_ID;
+
+ /**
* Predicate for matching a view by its id.
*/
private MatchIdPredicate mMatchIdPredicate;
@@ -3229,6 +3234,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
+ protected OnScrollChangeListener mOnScrollChangeListener;
+
/**
* Listeners for attach events.
*/
@@ -3886,6 +3893,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
case com.android.internal.R.styleable.View_contentDescription:
setContentDescription(a.getString(attr));
break;
+ case com.android.internal.R.styleable.View_accessibilityTraversalBefore:
+ setAccessibilityTraversalBefore(a.getResourceId(attr, NO_ID));
+ break;
+ case com.android.internal.R.styleable.View_accessibilityTraversalAfter:
+ setAccessibilityTraversalAfter(a.getResourceId(attr, NO_ID));
+ break;
case com.android.internal.R.styleable.View_labelFor:
setLabelFor(a.getResourceId(attr, NO_ID));
break;
@@ -4606,6 +4619,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Register a callback to be invoked when the scroll position of this view
+ * changed.
+ *
+ * @param l The callback that will run.
+ * @hide Only used internally.
+ */
+ public void setOnScrollChangeListener(OnScrollChangeListener l) {
+ getListenerInfo().mOnScrollChangeListener = l;
+ }
+
+ /**
* Register a callback to be invoked when focus of this view changed.
*
* @param l The callback that will run.
@@ -5529,12 +5553,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Gets the location of this view in screen coordintates.
+ * Gets the location of this view in screen coordinates.
*
* @param outRect The output location
* @hide
*/
public void getBoundsOnScreen(Rect outRect) {
+ getBoundsOnScreen(outRect, false);
+ }
+
+ /**
+ * Gets the location of this view in screen coordinates.
+ *
+ * @param outRect The output location
+ * @param clipToParent Whether to clip child bounds to the parent ones.
+ * @hide
+ */
+ public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
if (mAttachInfo == null) {
return;
}
@@ -5554,6 +5589,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
position.offset(-parentView.mScrollX, -parentView.mScrollY);
+ if (clipToParent) {
+ position.left = Math.max(position.left, 0);
+ position.top = Math.max(position.top, 0);
+ position.right = Math.min(position.right, parentView.getWidth());
+ position.bottom = Math.min(position.bottom, parentView.getHeight());
+ }
+
if (!parentView.hasIdentityMatrix()) {
parentView.getMatrix().mapRect(position);
}
@@ -5585,7 +5627,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
getDrawingRect(bounds);
info.setBoundsInParent(bounds);
- getBoundsOnScreen(bounds);
+ getBoundsOnScreen(bounds, true);
info.setBoundsInScreen(bounds);
ViewParent parent = getParentForAccessibility();
@@ -5598,6 +5640,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (rootView == null) {
rootView = this;
}
+
View label = rootView.findLabelForView(this, mID);
if (label != null) {
info.setLabeledBy(label);
@@ -5626,6 +5669,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
+ if (mAccessibilityTraversalBeforeId != View.NO_ID) {
+ View rootView = getRootView();
+ if (rootView == null) {
+ rootView = this;
+ }
+ View next = rootView.findViewInsideOutShouldExist(this,
+ mAccessibilityTraversalBeforeId);
+ if (next != null) {
+ info.setTraversalBefore(next);
+ }
+ }
+
+ if (mAccessibilityTraversalAfterId != View.NO_ID) {
+ View rootView = getRootView();
+ if (rootView == null) {
+ rootView = this;
+ }
+ View next = rootView.findViewInsideOutShouldExist(this,
+ mAccessibilityTraversalAfterId);
+ if (next != null) {
+ info.setTraversalAfter(next);
+ }
+ }
+
info.setVisibleToUser(isVisibleToUser());
info.setPackageName(mContext.getPackageName());
@@ -5756,136 +5823,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Computes a point on which a sequence of a down/up event can be sent to
- * trigger clicking this view. This method is for the exclusive use by the
- * accessibility layer to determine where to send a click event in explore
- * by touch mode.
- *
- * @param interactiveRegion The interactive portion of this window.
- * @param outPoint The point to populate.
- * @return True of such a point exists.
- */
- boolean computeClickPointInScreenForAccessibility(Region interactiveRegion,
- Point outPoint) {
- // Since the interactive portion of the view is a region but as a view
- // may have a transformation matrix which cannot be applied to a
- // region we compute the view bounds rectangle and all interactive
- // predecessor's and sibling's (siblings of predecessors included)
- // rectangles that intersect the view bounds. At the
- // end if the view was partially covered by another interactive
- // view we compute the view's interactive region and pick a point
- // on its boundary path as regions do not offer APIs to get inner
- // points. Note that the the code is optimized to fail early and
- // avoid unnecessary allocations plus computations.
-
- // The current approach has edge cases that may produce false
- // positives or false negatives. For example, a portion of the
- // view may be covered by an interactive descendant of a
- // predecessor, which we do not compute. Also a view may be handling
- // raw touch events instead registering click listeners, which
- // we cannot compute. Despite these limitations this approach will
- // work most of the time and it is a huge improvement over just
- // blindly sending the down and up events in the center of the
- // view.
-
- // Cannot click on an unattached view.
- if (mAttachInfo == null) {
- return false;
- }
-
- // Attached to an invisible window means this view is not visible.
- if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
- return false;
- }
-
- RectF bounds = mAttachInfo.mTmpTransformRect;
- bounds.set(0, 0, getWidth(), getHeight());
- List<RectF> intersections = mAttachInfo.mTmpRectList;
- intersections.clear();
-
- if (mParent instanceof ViewGroup) {
- ViewGroup parentGroup = (ViewGroup) mParent;
- if (!parentGroup.translateBoundsAndIntersectionsInWindowCoordinates(
- this, bounds, intersections)) {
- intersections.clear();
- return false;
- }
- }
-
- // Take into account the window location.
- final int dx = mAttachInfo.mWindowLeft;
- final int dy = mAttachInfo.mWindowTop;
- bounds.offset(dx, dy);
- offsetRects(intersections, dx, dy);
-
- if (intersections.isEmpty() && interactiveRegion == null) {
- outPoint.set((int) bounds.centerX(), (int) bounds.centerY());
- } else {
- // This view is partially covered by other views, then compute
- // the not covered region and pick a point on its boundary.
- Region region = new Region();
- region.set((int) bounds.left, (int) bounds.top,
- (int) bounds.right, (int) bounds.bottom);
-
- final int intersectionCount = intersections.size();
- for (int i = intersectionCount - 1; i >= 0; i--) {
- RectF intersection = intersections.remove(i);
- region.op((int) intersection.left, (int) intersection.top,
- (int) intersection.right, (int) intersection.bottom,
- Region.Op.DIFFERENCE);
- }
-
- // If the view is completely covered, done.
- if (region.isEmpty()) {
- return false;
- }
-
- // Take into account the interactive portion of the window
- // as the rest is covered by other windows. If no such a region
- // then the whole window is interactive.
- if (interactiveRegion != null) {
- region.op(interactiveRegion, Region.Op.INTERSECT);
- }
-
- // If the view is completely covered, done.
- if (region.isEmpty()) {
- return false;
- }
-
- // Try a shortcut here.
- if (region.isRect()) {
- Rect regionBounds = mAttachInfo.mTmpInvalRect;
- region.getBounds(regionBounds);
- outPoint.set(regionBounds.centerX(), regionBounds.centerY());
- return true;
- }
-
- // Get the a point on the region boundary path.
- Path path = region.getBoundaryPath();
- PathMeasure pathMeasure = new PathMeasure(path, false);
- final float[] coordinates = mAttachInfo.mTmpTransformLocation;
-
- // Without loss of generality pick a point.
- final float point = pathMeasure.getLength() * 0.01f;
- if (!pathMeasure.getPosTan(point, coordinates, null)) {
- return false;
- }
-
- outPoint.set(Math.round(coordinates[0]), Math.round(coordinates[1]));
- }
-
- return true;
- }
-
- static void offsetRects(List<RectF> rects, float offsetX, float offsetY) {
- final int rectCount = rects.size();
- for (int i = 0; i < rectCount; i++) {
- RectF intersection = rects.get(i);
- intersection.offset(offsetX, offsetY);
- }
- }
-
- /**
* Returns the delegate for implementing accessibility support via
* composition. For more details see {@link AccessibilityDelegate}.
*
@@ -6015,6 +5952,94 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Sets the id of a view before which this one is visited in accessibility traversal.
+ * A screen-reader must visit the content of this view before the content of the one
+ * it precedes. For example, if view B is set to be before view A, then a screen-reader
+ * will traverse the entire content of B before traversing the entire content of A,
+ * regardles of what traversal strategy it is using.
+ * <p>
+ * Views that do not have specified before/after relationships are traversed in order
+ * determined by the screen-reader.
+ * </p>
+ * <p>
+ * Setting that this view is before a view that is not important for accessibility
+ * or if this view is not important for accessibility will have no effect as the
+ * screen-reader is not aware of unimportant views.
+ * </p>
+ *
+ * @param beforeId The id of a view this one precedes in accessibility traversal.
+ *
+ * @attr ref android.R.styleable#View_accessibilityTraversalBefore
+ *
+ * @see #setImportantForAccessibility(int)
+ */
+ @RemotableViewMethod
+ public void setAccessibilityTraversalBefore(int beforeId) {
+ if (mAccessibilityTraversalBeforeId == beforeId) {
+ return;
+ }
+ mAccessibilityTraversalBeforeId = beforeId;
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+
+ /**
+ * Gets the id of a view before which this one is visited in accessibility traversal.
+ *
+ * @return The id of a view this one precedes in accessibility traversal if
+ * specified, otherwise {@link #NO_ID}.
+ *
+ * @see #setAccessibilityTraversalBefore(int)
+ */
+ public int getAccessibilityTraversalBefore() {
+ return mAccessibilityTraversalBeforeId;
+ }
+
+ /**
+ * Sets the id of a view after which this one is visited in accessibility traversal.
+ * A screen-reader must visit the content of the other view before the content of this
+ * one. For example, if view B is set to be after view A, then a screen-reader
+ * will traverse the entire content of A before traversing the entire content of B,
+ * regardles of what traversal strategy it is using.
+ * <p>
+ * Views that do not have specified before/after relationships are traversed in order
+ * determined by the screen-reader.
+ * </p>
+ * <p>
+ * Setting that this view is after a view that is not important for accessibility
+ * or if this view is not important for accessibility will have no effect as the
+ * screen-reader is not aware of unimportant views.
+ * </p>
+ *
+ * @param afterId The id of a view this one succedees in accessibility traversal.
+ *
+ * @attr ref android.R.styleable#View_accessibilityTraversalAfter
+ *
+ * @see #setImportantForAccessibility(int)
+ */
+ @RemotableViewMethod
+ public void setAccessibilityTraversalAfter(int afterId) {
+ if (mAccessibilityTraversalAfterId == afterId) {
+ return;
+ }
+ mAccessibilityTraversalAfterId = afterId;
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+
+ /**
+ * Gets the id of a view after which this one is visited in accessibility traversal.
+ *
+ * @return The id of a view this one succeedes in accessibility traversal if
+ * specified, otherwise {@link #NO_ID}.
+ *
+ * @see #setAccessibilityTraversalAfter(int)
+ */
+ public int getAccessibilityTraversalAfter() {
+ return mAccessibilityTraversalAfterId;
+ }
+
+ /**
* Gets the id of a view for which this view serves as a label for
* accessibility purposes.
*
@@ -6033,11 +6058,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@RemotableViewMethod
public void setLabelFor(int id) {
+ if (mLabelForId == id) {
+ return;
+ }
mLabelForId = id;
if (mLabelForId != View.NO_ID
&& mID == View.NO_ID) {
mID = generateViewId();
}
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
/**
@@ -7994,6 +8024,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Report an accessibility action to this view's parents for delegated processing.
+ *
+ * <p>Implementations of {@link #performAccessibilityAction(int, Bundle)} may internally
+ * call this method to delegate an accessibility action to a supporting parent. If the parent
+ * returns true from its
+ * {@link ViewParent#onNestedPrePerformAccessibilityAction(View, int, android.os.Bundle)}
+ * method this method will return true to signify that the action was consumed.</p>
+ *
+ * <p>This method is useful for implementing nested scrolling child views. If
+ * {@link #isNestedScrollingEnabled()} returns true and the action is a scrolling action
+ * a custom view implementation may invoke this method to allow a parent to consume the
+ * scroll first. If this method returns true the custom view should skip its own scrolling
+ * behavior.</p>
+ *
+ * @param action Accessibility action to delegate
+ * @param arguments Optional action arguments
+ * @return true if the action was consumed by a parent
+ */
+ public boolean dispatchNestedPrePerformAccessibilityAction(int action, Bundle arguments) {
+ for (ViewParent p = getParent(); p != null; p = p.getParent()) {
+ if (p.onNestedPrePerformAccessibilityAction(this, action, arguments)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Performs the specified accessibility action on the view. For
* possible accessibility actions look at {@link AccessibilityNodeInfo}.
* <p>
@@ -8003,6 +8061,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* is responsible for handling this call.
* </p>
*
+ * <p>The default implementation will delegate
+ * {@link AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD} and
+ * {@link AccessibilityNodeInfo#ACTION_SCROLL_FORWARD} to nested scrolling parents if
+ * {@link #isNestedScrollingEnabled() nested scrolling is enabled} on this view.</p>
+ *
* @param action The action to perform.
* @param arguments Optional action arguments.
* @return Whether the action was performed.
@@ -8019,8 +8082,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #performAccessibilityAction(int, Bundle)
*
* Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide Until we've refactored all accessibility delegation methods.
*/
- boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (isNestedScrollingEnabled()
+ && (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD
+ || action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)) {
+ if (dispatchNestedPrePerformAccessibilityAction(action, arguments)) {
+ return true;
+ }
+ }
+
switch (action) {
case AccessibilityNodeInfo.ACTION_CLICK: {
if (isClickable()) {
@@ -8364,6 +8437,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
+ // If the event should be handled by accessibility focus first.
+ if (event.isTargetAccessibilityFocus()) {
+ // We don't have focus or no virtual descendant has it, do not handle the event.
+ if (!isAccessibilityFocusedViewOrHost()) {
+ return false;
+ }
+ // We have focus and got the event, then use normal event dispatch.
+ event.setTargetAccessibilityFocus(false);
+ }
+
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
@@ -8406,6 +8489,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return result;
}
+ boolean isAccessibilityFocusedViewOrHost() {
+ return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl()
+ .getAccessibilityFocusedHost() == this);
+ }
+
/**
* Filter the touch event to apply security policies.
*
@@ -9779,6 +9867,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (ai != null) {
ai.mViewScrollChanged = true;
}
+
+ if (mListenerInfo != null && mListenerInfo.mOnScrollChangeListener != null) {
+ mListenerInfo.mOnScrollChangeListener.onScrollChange(this, l, t, oldl, oldt);
+ }
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the scroll
+ * position of a view changes.
+ *
+ * @hide Only used internally.
+ */
+ public interface OnScrollChangeListener {
+ /**
+ * Called when the scroll position of a view changes.
+ *
+ * @param v The view whose scroll position has changed.
+ * @param scrollX Current horizontal scroll origin.
+ * @param scrollY Current vertical scroll origin.
+ * @param oldScrollX Previous horizontal scroll origin.
+ * @param oldScrollY Previous vertical scroll origin.
+ */
+ void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY);
}
/**
@@ -11086,7 +11197,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #setOutlineProvider(ViewOutlineProvider)
*/
public void invalidateOutline() {
- mPrivateFlags3 |= PFLAG3_OUTLINE_INVALID;
+ rebuildOutline();
notifySubtreeAccessibilityStateChangedIfNeeded();
invalidateViewProperty(false, false);
@@ -11657,7 +11768,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if (mGhostView != null) {
- mGhostView.invalidate(invalidateCache);
+ mGhostView.invalidate(true);
return;
}
@@ -12765,7 +12876,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
Interpolator.Result.FREEZE_END) {
cache.state = ScrollabilityCache.OFF;
} else {
- cache.scrollBar.setAlpha(Math.round(values[0]));
+ cache.scrollBar.mutate().setAlpha(Math.round(values[0]));
}
// This will make the scroll bars inval themselves after
@@ -12775,7 +12886,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
} else {
// We're just on -- but we may have been fading before so
// reset alpha
- cache.scrollBar.setAlpha(255);
+ cache.scrollBar.mutate().setAlpha(255);
}
@@ -12989,7 +13100,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
// Should resolve Drawables before Padding because we need the layout direction of the
// Drawable to correctly resolve Padding.
- if (!isDrawablesResolved()) {
+ if (!areDrawablesResolved()) {
resolveDrawables();
}
if (!isPaddingResolved()) {
@@ -13253,6 +13364,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
public void resetResolvedPadding() {
+ resetResolvedPaddingInternal();
+ }
+
+ /**
+ * Used when we only want to reset *this* view's padding and not trigger overrides
+ * in ViewGroup that reset children too.
+ */
+ void resetResolvedPaddingInternal() {
mPrivateFlags2 &= ~PFLAG2_PADDING_RESOLVED;
}
@@ -14047,7 +14166,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
} else {
draw(canvas);
}
- drawAccessibilityFocus(canvas);
}
} finally {
renderNode.end(canvas);
@@ -14213,144 +14331,158 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public void buildDrawingCache(boolean autoScale) {
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
mDrawingCache == null : mUnscaledDrawingCache == null)) {
- mCachingFailed = false;
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "buildDrawingCache/SW Layer for " + getClass().getSimpleName());
+ }
+ try {
+ buildDrawingCacheImpl(autoScale);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+ }
- int width = mRight - mLeft;
- int height = mBottom - mTop;
+ /**
+ * private, internal implementation of buildDrawingCache, used to enable tracing
+ */
+ private void buildDrawingCacheImpl(boolean autoScale) {
+ mCachingFailed = false;
- final AttachInfo attachInfo = mAttachInfo;
- final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;
+ int width = mRight - mLeft;
+ int height = mBottom - mTop;
- if (autoScale && scalingRequired) {
- width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);
- height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);
+ final AttachInfo attachInfo = mAttachInfo;
+ final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;
+
+ if (autoScale && scalingRequired) {
+ width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);
+ height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);
+ }
+
+ final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;
+ final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque();
+ final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache;
+
+ final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
+ final long drawingCacheSize =
+ ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();
+ if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {
+ if (width > 0 && height > 0) {
+ Log.w(VIEW_LOG_TAG, "View too large to fit into drawing cache, needs "
+ + projectedBitmapSize + " bytes, only "
+ + drawingCacheSize + " available");
}
+ destroyDrawingCache();
+ mCachingFailed = true;
+ return;
+ }
- final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;
- final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque();
- final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache;
+ boolean clear = true;
+ Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;
- final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
- final long drawingCacheSize =
- ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();
- if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {
- if (width > 0 && height > 0) {
- Log.w(VIEW_LOG_TAG, "View too large to fit into drawing cache, needs "
- + projectedBitmapSize + " bytes, only "
- + drawingCacheSize + " available");
+ if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
+ Bitmap.Config quality;
+ if (!opaque) {
+ // Never pick ARGB_4444 because it looks awful
+ // Keep the DRAWING_CACHE_QUALITY_LOW flag just in case
+ switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) {
+ case DRAWING_CACHE_QUALITY_AUTO:
+ case DRAWING_CACHE_QUALITY_LOW:
+ case DRAWING_CACHE_QUALITY_HIGH:
+ default:
+ quality = Bitmap.Config.ARGB_8888;
+ break;
}
- destroyDrawingCache();
- mCachingFailed = true;
- return;
+ } else {
+ // Optimization for translucent windows
+ // If the window is translucent, use a 32 bits bitmap to benefit from memcpy()
+ quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
}
- boolean clear = true;
- Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;
+ // Try to cleanup memory
+ if (bitmap != null) bitmap.recycle();
- if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
- Bitmap.Config quality;
- if (!opaque) {
- // Never pick ARGB_4444 because it looks awful
- // Keep the DRAWING_CACHE_QUALITY_LOW flag just in case
- switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) {
- case DRAWING_CACHE_QUALITY_AUTO:
- case DRAWING_CACHE_QUALITY_LOW:
- case DRAWING_CACHE_QUALITY_HIGH:
- default:
- quality = Bitmap.Config.ARGB_8888;
- break;
- }
+ try {
+ bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
+ width, height, quality);
+ bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
+ if (autoScale) {
+ mDrawingCache = bitmap;
} else {
- // Optimization for translucent windows
- // If the window is translucent, use a 32 bits bitmap to benefit from memcpy()
- quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
+ mUnscaledDrawingCache = bitmap;
}
-
- // Try to cleanup memory
- if (bitmap != null) bitmap.recycle();
-
- try {
- bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
- width, height, quality);
- bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
- if (autoScale) {
- mDrawingCache = bitmap;
- } else {
- mUnscaledDrawingCache = bitmap;
- }
- if (opaque && use32BitCache) bitmap.setHasAlpha(false);
- } catch (OutOfMemoryError e) {
- // If there is not enough memory to create the bitmap cache, just
- // ignore the issue as bitmap caches are not required to draw the
- // view hierarchy
- if (autoScale) {
- mDrawingCache = null;
- } else {
- mUnscaledDrawingCache = null;
- }
- mCachingFailed = true;
- return;
+ if (opaque && use32BitCache) bitmap.setHasAlpha(false);
+ } catch (OutOfMemoryError e) {
+ // If there is not enough memory to create the bitmap cache, just
+ // ignore the issue as bitmap caches are not required to draw the
+ // view hierarchy
+ if (autoScale) {
+ mDrawingCache = null;
+ } else {
+ mUnscaledDrawingCache = null;
}
-
- clear = drawingCacheBackgroundColor != 0;
+ mCachingFailed = true;
+ return;
}
- Canvas canvas;
- if (attachInfo != null) {
- canvas = attachInfo.mCanvas;
- if (canvas == null) {
- canvas = new Canvas();
- }
- canvas.setBitmap(bitmap);
- // Temporarily clobber the cached Canvas in case one of our children
- // is also using a drawing cache. Without this, the children would
- // steal the canvas by attaching their own bitmap to it and bad, bad
- // thing would happen (invisible views, corrupted drawings, etc.)
- attachInfo.mCanvas = null;
- } else {
- // This case should hopefully never or seldom happen
- canvas = new Canvas(bitmap);
- }
+ clear = drawingCacheBackgroundColor != 0;
+ }
- if (clear) {
- bitmap.eraseColor(drawingCacheBackgroundColor);
+ Canvas canvas;
+ if (attachInfo != null) {
+ canvas = attachInfo.mCanvas;
+ if (canvas == null) {
+ canvas = new Canvas();
}
+ canvas.setBitmap(bitmap);
+ // Temporarily clobber the cached Canvas in case one of our children
+ // is also using a drawing cache. Without this, the children would
+ // steal the canvas by attaching their own bitmap to it and bad, bad
+ // thing would happen (invisible views, corrupted drawings, etc.)
+ attachInfo.mCanvas = null;
+ } else {
+ // This case should hopefully never or seldom happen
+ canvas = new Canvas(bitmap);
+ }
- computeScroll();
- final int restoreCount = canvas.save();
+ if (clear) {
+ bitmap.eraseColor(drawingCacheBackgroundColor);
+ }
- if (autoScale && scalingRequired) {
- final float scale = attachInfo.mApplicationScale;
- canvas.scale(scale, scale);
- }
+ computeScroll();
+ final int restoreCount = canvas.save();
- canvas.translate(-mScrollX, -mScrollY);
+ if (autoScale && scalingRequired) {
+ final float scale = attachInfo.mApplicationScale;
+ canvas.scale(scale, scale);
+ }
- mPrivateFlags |= PFLAG_DRAWN;
- if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
- mLayerType != LAYER_TYPE_NONE) {
- mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
- }
+ canvas.translate(-mScrollX, -mScrollY);
- // Fast path for layouts with no backgrounds
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
- mPrivateFlags &= ~PFLAG_DIRTY_MASK;
- dispatchDraw(canvas);
- if (mOverlay != null && !mOverlay.isEmpty()) {
- mOverlay.getOverlayView().draw(canvas);
- }
- } else {
- draw(canvas);
+ mPrivateFlags |= PFLAG_DRAWN;
+ if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
+ mLayerType != LAYER_TYPE_NONE) {
+ mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
+ }
+
+ // Fast path for layouts with no backgrounds
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+ dispatchDraw(canvas);
+ if (mOverlay != null && !mOverlay.isEmpty()) {
+ mOverlay.getOverlayView().draw(canvas);
}
- drawAccessibilityFocus(canvas);
+ } else {
+ draw(canvas);
+ }
- canvas.restoreToCount(restoreCount);
- canvas.setBitmap(null);
+ canvas.restoreToCount(restoreCount);
+ canvas.setBitmap(null);
- if (attachInfo != null) {
- // Restore the cached Canvas for our siblings
- attachInfo.mCanvas = canvas;
- }
+ if (attachInfo != null) {
+ // Restore the cached Canvas for our siblings
+ attachInfo.mCanvas = canvas;
}
}
@@ -14417,7 +14549,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
} else {
draw(canvas);
}
- drawAccessibilityFocus(canvas);
mPrivateFlags = flags;
@@ -14578,27 +14709,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* this view, to which future drawing operations will be clipped.
*/
public void setClipBounds(Rect clipBounds) {
+ if (clipBounds == mClipBounds
+ || (clipBounds != null && clipBounds.equals(mClipBounds))) {
+ return;
+ }
if (clipBounds != null) {
- if (clipBounds.equals(mClipBounds)) {
- return;
- }
if (mClipBounds == null) {
- invalidate();
mClipBounds = new Rect(clipBounds);
} else {
- invalidate(Math.min(mClipBounds.left, clipBounds.left),
- Math.min(mClipBounds.top, clipBounds.top),
- Math.max(mClipBounds.right, clipBounds.right),
- Math.max(mClipBounds.bottom, clipBounds.bottom));
mClipBounds.set(clipBounds);
}
} else {
- if (mClipBounds != null) {
- invalidate();
- mClipBounds = null;
- }
+ mClipBounds = null;
}
mRenderNode.setClipBounds(mClipBounds);
+ invalidateViewProperty(false, false);
}
/**
@@ -14677,10 +14802,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
void setDisplayListProperties(RenderNode renderNode) {
if (renderNode != null) {
- if ((mPrivateFlags3 & PFLAG3_OUTLINE_INVALID) != 0) {
- rebuildOutline();
- mPrivateFlags3 &= ~PFLAG3_OUTLINE_INVALID;
- }
renderNode.setHasOverlappingRendering(hasOverlappingRendering());
if (mParent instanceof ViewGroup) {
renderNode.setClipToBounds(
@@ -15015,13 +15136,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
- if (mOverlay != null && !mOverlay.isEmpty()) {
- mOverlay.getOverlayView().draw(canvas);
- }
} else {
draw(canvas);
}
- drawAccessibilityFocus(canvas);
} else {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((HardwareCanvas) canvas).drawRenderNode(renderNode, null, flags);
@@ -15273,50 +15390,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Draws the accessibility focus rect onto the specified canvas.
- *
- * @param canvas Canvas on which to draw the focus rect
- */
- private void drawAccessibilityFocus(Canvas canvas) {
- if (mAttachInfo == null) {
- return;
- }
-
- final Rect bounds = mAttachInfo.mTmpInvalRect;
- final ViewRootImpl viewRoot = getViewRootImpl();
- if (viewRoot == null || viewRoot.getAccessibilityFocusedHost() != this) {
- return;
- }
-
- final AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
- if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) {
- return;
- }
-
- final Drawable drawable = viewRoot.getAccessibilityFocusedDrawable();
- if (drawable == null) {
- return;
- }
-
- final AccessibilityNodeInfo virtualView = viewRoot.getAccessibilityFocusedVirtualView();
- if (virtualView != null) {
- virtualView.getBoundsInScreen(bounds);
- final int[] offset = mAttachInfo.mTmpLocation;
- getLocationOnScreen(offset);
- bounds.offset(-offset[0], -offset[1]);
- } else {
- bounds.set(0, 0, mRight - mLeft, mBottom - mTop);
- }
-
- canvas.save();
- canvas.translate(mScrollX, mScrollY);
- canvas.clipRect(bounds, Region.Op.REPLACE);
- drawable.setBounds(bounds);
- drawable.draw(canvas);
- canvas.restore();
- }
-
- /**
* Draws the background onto the specified canvas.
*
* @param canvas Canvas on which to draw the background
@@ -15330,7 +15403,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
- mPrivateFlags3 |= PFLAG3_OUTLINE_INVALID;
+ rebuildOutline();
}
// Attempt to use a display list if requested.
@@ -15338,10 +15411,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
&& mAttachInfo.mHardwareRenderer != null) {
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
- final RenderNode displayList = mBackgroundRenderNode;
- if (displayList != null && displayList.isValid()) {
- setBackgroundDisplayListProperties(displayList);
- ((HardwareCanvas) canvas).drawRenderNode(displayList);
+ final RenderNode renderNode = mBackgroundRenderNode;
+ if (renderNode != null && renderNode.isValid()) {
+ setBackgroundRenderNodeProperties(renderNode);
+ ((HardwareCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
@@ -15357,14 +15430,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
- /**
- * Set up background drawable display list properties.
- *
- * @param displayList Valid display list for the background drawable
- */
- private void setBackgroundDisplayListProperties(RenderNode displayList) {
- displayList.setTranslationX(mScrollX);
- displayList.setTranslationY(mScrollY);
+ private void setBackgroundRenderNodeProperties(RenderNode renderNode) {
+ renderNode.setTranslationX(mScrollX);
+ renderNode.setTranslationY(mScrollY);
}
/**
@@ -15384,6 +15452,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final int width = bounds.width();
final int height = bounds.height();
final HardwareCanvas canvas = renderNode.start(width, height);
+
+ // Reverse left/top translation done by drawable canvas, which will
+ // instead be applied by rendernode's LTRB bounds below. This way, the
+ // drawable's bounds match with its rendernode bounds and its content
+ // will lie within those bounds in the rendernode tree.
+ canvas.translate(-bounds.left, -bounds.top);
+
try {
drawable.draw(canvas);
} finally {
@@ -15676,7 +15751,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
- if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+ if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
// If we are visible, force the DRAWN bit to on so that
// this invalidate will go through (at least to our parent).
// This is because someone may have invalidated this view
@@ -15699,13 +15774,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return changed;
}
+ /**
+ * Same as setFrame, but public and hidden. For use in {@link android.transition.ChangeBounds}.
+ * @hide
+ */
+ public void setLeftTopRightBottom(int left, int top, int right, int bottom) {
+ setFrame(left, top, right, bottom);
+ }
+
private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
if (mOverlay != null) {
mOverlay.getOverlayView().setRight(newWidth);
mOverlay.getOverlayView().setBottom(newHeight);
}
- mPrivateFlags3 |= PFLAG3_OUTLINE_INVALID;
+ rebuildOutline();
}
/**
@@ -15741,8 +15824,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
-
- mPrivateFlags3 |= PFLAG3_OUTLINE_INVALID;
+ rebuildOutline();
}
}
@@ -15832,6 +15914,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
onResolveDrawables(layoutDirection);
}
+ boolean areDrawablesResolved() {
+ return (mPrivateFlags2 & PFLAG2_DRAWABLE_RESOLVED) == PFLAG2_DRAWABLE_RESOLVED;
+ }
+
/**
* Called when layout direction has been resolved.
*
@@ -15851,11 +15937,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
protected void resetResolvedDrawables() {
- mPrivateFlags2 &= ~PFLAG2_DRAWABLE_RESOLVED;
+ resetResolvedDrawablesInternal();
}
- private boolean isDrawablesResolved() {
- return (mPrivateFlags2 & PFLAG2_DRAWABLE_RESOLVED) == PFLAG2_DRAWABLE_RESOLVED;
+ void resetResolvedDrawablesInternal() {
+ mPrivateFlags2 &= ~PFLAG2_DRAWABLE_RESOLVED;
}
/**
@@ -15905,7 +15991,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* This function is called whenever the view hotspot changes and needs to
- * be propagated to drawables managed by the view.
+ * be propagated to drawables or child views managed by the view.
+ * <p>
+ * Dispatching to child views is handled by
+ * {@link #dispatchDrawableHotspotChanged(float, float)}.
* <p>
* Be sure to call through to the superclass when overriding this function.
*
@@ -15916,6 +16005,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (mBackground != null) {
mBackground.setHotspot(x, y);
}
+
+ dispatchDrawableHotspotChanged(x, y);
+ }
+
+ /**
+ * Dispatches drawableHotspotChanged to all of this View's children.
+ *
+ * @param x hotspot x coordinate
+ * @param y hotspot y coordinate
+ * @see #drawableHotspotChanged(float, float)
+ */
+ public void dispatchDrawableHotspotChanged(float x, float y) {
}
/**
@@ -16155,10 +16256,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
padding = new Rect();
sThreadLocal.set(padding);
}
- resetResolvedDrawables();
+ resetResolvedDrawablesInternal();
background.setLayoutDirection(getLayoutDirection());
if (background.getPadding(padding)) {
- resetResolvedPadding();
+ resetResolvedPaddingInternal();
switch (background.getLayoutDirection()) {
case LAYOUT_DIRECTION_RTL:
mUserPaddingLeftInitial = padding.right;
@@ -16330,6 +16431,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (tintInfo.mHasTintMode) {
mBackground.setTintMode(tintInfo.mTintMode);
}
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mBackground.isStateful()) {
+ mBackground.setState(getDrawableState());
+ }
}
}
}
@@ -16352,7 +16459,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param bottom the bottom padding in pixels
*/
public void setPadding(int left, int top, int right, int bottom) {
- resetResolvedPadding();
+ resetResolvedPaddingInternal();
mUserPaddingStart = UNDEFINED_PADDING;
mUserPaddingEnd = UNDEFINED_PADDING;
@@ -16423,6 +16530,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (changed) {
requestLayout();
+ invalidateOutline();
}
}
@@ -16444,7 +16552,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param bottom the bottom padding in pixels
*/
public void setPaddingRelative(int start, int top, int end, int bottom) {
- resetResolvedPadding();
+ resetResolvedPaddingInternal();
mUserPaddingStart = start;
mUserPaddingEnd = end;
@@ -16617,8 +16725,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
invalidate(true);
refreshDrawableState();
dispatchSetSelected(selected);
- notifyViewAccessibilityStateChangedIfNeeded(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ if (selected) {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ } else {
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
}
}
@@ -17414,17 +17526,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
- if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
- widthMeasureSpec != mOldWidthMeasureSpec ||
- heightMeasureSpec != mOldHeightMeasureSpec) {
+ final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
+ final boolean isExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
+ MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
+ final boolean matchingSize = isExactly &&
+ getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) &&
+ getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
+ if (forceLayout || !matchingSize &&
+ (widthMeasureSpec != mOldWidthMeasureSpec ||
+ heightMeasureSpec != mOldHeightMeasureSpec)) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
- int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
- mMeasureCache.indexOfKey(key);
+ int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
diff --git a/core/java/android/view/ViewAnimationUtils.java b/core/java/android/view/ViewAnimationUtils.java
index 001cd01..d44df31 100644
--- a/core/java/android/view/ViewAnimationUtils.java
+++ b/core/java/android/view/ViewAnimationUtils.java
@@ -17,6 +17,7 @@
package android.view;
import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
import android.animation.RevealAnimator;
/**
@@ -35,7 +36,11 @@ public final class ViewAnimationUtils {
* {@link View#setClipToOutline(boolean) View Outline clipping}.
* <p>
* Note that the animation returned here is a one-shot animation. It cannot
- * be re-used, and once started it cannot be paused or resumed.
+ * be re-used, and once started it cannot be paused or resumed. It is also
+ * an asynchronous animation that automatically runs off of the UI thread.
+ * As a result {@link AnimatorListener#onAnimationEnd(Animator)}
+ * will occur after the animation has ended, but it may be delayed depending
+ * on thread responsiveness.
*
* @param view The View will be clipped to the animating circle.
* @param centerX The x coordinate of the center of the animating circle.
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 12a49d5..50e64c6 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -1005,12 +1005,21 @@ public class ViewDebug {
return fields;
}
- final ArrayList<Field> foundFields = new ArrayList<Field>();
- fields = klass.getDeclaredFields();
+ final ArrayList<Field> declaredFields = new ArrayList();
+ klass.getDeclaredFieldsUnchecked(false, declaredFields);
- int count = fields.length;
+ final ArrayList<Field> foundFields = new ArrayList<Field>();
+ final int count = declaredFields.size();
for (int i = 0; i < count; i++) {
- final Field field = fields[i];
+ final Field field = declaredFields.get(i);
+
+ // Ensure the field type can be resolved.
+ try {
+ field.getType();
+ } catch (NoClassDefFoundError e) {
+ continue;
+ }
+
if (field.isAnnotationPresent(ExportedProperty.class)) {
field.setAccessible(true);
foundFields.add(field);
@@ -1039,12 +1048,22 @@ public class ViewDebug {
return methods;
}
- final ArrayList<Method> foundMethods = new ArrayList<Method>();
- methods = klass.getDeclaredMethods();
+ final ArrayList<Method> declaredMethods = new ArrayList();
+ klass.getDeclaredMethodsUnchecked(false, declaredMethods);
- int count = methods.length;
+ final ArrayList<Method> foundMethods = new ArrayList<Method>();
+ final int count = declaredMethods.size();
for (int i = 0; i < count; i++) {
- final Method method = methods[i];
+ final Method method = declaredMethods.get(i);
+
+ // Ensure the method return and parameter types can be resolved.
+ try {
+ method.getReturnType();
+ method.getParameterTypes();
+ } catch (NoClassDefFoundError e) {
+ continue;
+ }
+
if (method.getParameterTypes().length == 0 &&
method.isAnnotationPresent(ExportedProperty.class) &&
method.getReturnType() != Void.class) {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 32d1a88..f8026d1 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -32,6 +32,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.os.Build;
+import android.os.Bundle;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
@@ -51,8 +52,10 @@ import com.android.internal.util.Predicate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.NoSuchElementException;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
@@ -159,6 +162,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// Used during drag dispatch
private PointF mLocalPoint;
+ // Lazily-created holder for point computations.
+ private float[] mTempPoint;
+
// Layout animation
private LayoutAnimationController mLayoutAnimationController;
private Animation.AnimationListener mAnimationListener;
@@ -771,117 +777,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * Translates the given bounds and intersections from child coordinates to
- * local coordinates. In case any interactive sibling of the calling child
- * covers the latter, a new intersections is added to the intersection list.
- * This method is for the exclusive use by the accessibility layer to compute
- * a point where a sequence of down and up events would click on a view.
- *
- * @param child The child making the call.
- * @param bounds The bounds to translate in child coordinates.
- * @param intersections The intersections of interactive views covering the child.
- * @return True if the bounds and intersections were computed, false otherwise.
- */
- boolean translateBoundsAndIntersectionsInWindowCoordinates(View child,
- RectF bounds, List<RectF> intersections) {
- // Not attached, done.
- if (mAttachInfo == null) {
- return false;
- }
-
- if (getAlpha() <= 0 || getTransitionAlpha() <= 0 ||
- getVisibility() != VISIBLE) {
- // Cannot click on a view with an invisible predecessor.
- return false;
- }
-
- // Compensate for the child transformation.
- if (!child.hasIdentityMatrix()) {
- Matrix matrix = child.getMatrix();
- matrix.mapRect(bounds);
- final int intersectionCount = intersections.size();
- for (int i = 0; i < intersectionCount; i++) {
- RectF intersection = intersections.get(i);
- matrix.mapRect(intersection);
- }
- }
-
- // Translate the bounds from child to parent coordinates.
- final int dx = child.mLeft - mScrollX;
- final int dy = child.mTop - mScrollY;
- bounds.offset(dx, dy);
- offsetRects(intersections, dx, dy);
-
- // If the bounds do not intersect our bounds, done.
- if (!bounds.intersects(0, 0, getWidth(), getHeight())) {
- return false;
- }
-
- // Check whether any clickable siblings cover the child
- // view and if so keep track of the intersections. Also
- // respect Z ordering when iterating over children.
- ArrayList<View> orderedList = buildOrderedChildList();
- final boolean useCustomOrder = orderedList == null
- && isChildrenDrawingOrderEnabled();
-
- final int childCount = mChildrenCount;
- for (int i = childCount - 1; i >= 0; i--) {
- final int childIndex = useCustomOrder
- ? getChildDrawingOrder(childCount, i) : i;
- final View sibling = (orderedList == null)
- ? mChildren[childIndex] : orderedList.get(childIndex);
-
- // We care only about siblings over the child.
- if (sibling == child) {
- break;
- }
-
- // Ignore invisible views as they are not interactive.
- if (sibling.getVisibility() != View.VISIBLE) {
- continue;
- }
-
- // If sibling is not interactive we do not care.
- if (!sibling.isClickable() && !sibling.isLongClickable()) {
- continue;
- }
-
- // Compute the sibling bounds in its coordinates.
- RectF siblingBounds = mAttachInfo.mTmpTransformRect1;
- siblingBounds.set(0, 0, sibling.getWidth(), sibling.getHeight());
-
- // Take into account the sibling transformation matrix.
- if (!sibling.hasIdentityMatrix()) {
- sibling.getMatrix().mapRect(siblingBounds);
- }
-
- // Offset the sibling to our coordinates.
- final int siblingDx = sibling.mLeft - mScrollX;
- final int siblingDy = sibling.mTop - mScrollY;
- siblingBounds.offset(siblingDx, siblingDy);
-
- // Compute the intersection between the child and the sibling.
- if (siblingBounds.intersect(bounds)) {
- // If an interactive sibling completely covers the child, done.
- if (siblingBounds.equals(bounds)) {
- return false;
- }
- // Keep track of the intersection rectangle.
- RectF intersection = new RectF(siblingBounds);
- intersections.add(intersection);
- }
- }
-
- if (mParent instanceof ViewGroup) {
- ViewGroup parentGroup = (ViewGroup) mParent;
- return parentGroup.translateBoundsAndIntersectionsInWindowCoordinates(
- this, bounds, intersections);
- }
-
- return true;
- }
-
- /**
* Called when a child view has changed whether or not it is tracking transient state.
*/
public void childHasTransientStateChanged(View child, boolean childHasTransientState) {
@@ -2036,6 +1931,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
+ // If the event targets the accessibility focused view and this is it, start
+ // normal event dispatch. Maybe a descendant is what will handle the click.
+ if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
+ ev.setTargetAccessibilityFocus(false);
+ }
+
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
@@ -2067,6 +1968,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
intercepted = true;
}
+ // If intercepted, start normal event dispatch. Also if there is already
+ // a view that is handling the gesture, do normal event dispatch.
+ if (intercepted || mFirstTouchTarget != null) {
+ ev.setTargetAccessibilityFocus(false);
+ }
+
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
@@ -2076,6 +1983,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
+
+ // If the event is targeting accessiiblity focus we give it to the
+ // view that has accessibility focus and if it does not handle it
+ // we clear the flag and dispatch the event to all children as usual.
+ // We are looking up the accessibility focused host to avoid keeping
+ // state since these events are very rare.
+ View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
+ ? findChildWithAccessibilityFocus() : null;
+
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
@@ -2102,8 +2018,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
+
+ // If there is a view that has accessibility focus we want it
+ // to get the event first and if not handled we will perform a
+ // normal dispatch. We may do a double iteration but this is
+ // safer given the timeframe.
+ if (childWithAccessibilityFocus != null) {
+ if (childWithAccessibilityFocus != child) {
+ continue;
+ }
+ childWithAccessibilityFocus = null;
+ i = childrenCount - 1;
+ }
+
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
+ ev.setTargetAccessibilityFocus(false);
continue;
}
@@ -2136,6 +2066,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
alreadyDispatchedToNewTouchTarget = true;
break;
}
+
+ // The accessibility focus didn't handle the event, so clear
+ // the flag and do a normal dispatch to all children.
+ ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
@@ -2208,6 +2142,34 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * Finds the child which has accessibility focus.
+ *
+ * @return The child that has focus.
+ */
+ private View findChildWithAccessibilityFocus() {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot == null) {
+ return null;
+ }
+
+ View current = viewRoot.getAccessibilityFocusedHost();
+ if (current == null) {
+ return null;
+ }
+
+ ViewParent parent = current.getParent();
+ while (parent instanceof View) {
+ if (parent == this) {
+ return current;
+ }
+ current = (View) parent;
+ parent = current.getParent();
+ }
+
+ return null;
+ }
+
+ /**
* Resets all touch state in preparation for a new cycle.
*/
private void resetTouchState() {
@@ -2355,6 +2317,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|| child.getAnimation() != null;
}
+ private float[] getTempPoint() {
+ if (mTempPoint == null) {
+ mTempPoint = new float[2];
+ }
+ return mTempPoint;
+ }
+
/**
* Returns true if a child view contains the specified point when transformed
* into its coordinate space.
@@ -2363,24 +2332,30 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
- float localX = x + mScrollX - child.mLeft;
- float localY = y + mScrollY - child.mTop;
- if (! child.hasIdentityMatrix() && mAttachInfo != null) {
- final float[] localXY = mAttachInfo.mTmpTransformLocation;
- localXY[0] = localX;
- localXY[1] = localY;
- child.getInverseMatrix().mapPoints(localXY);
- localX = localXY[0];
- localY = localXY[1];
- }
- final boolean isInView = child.pointInView(localX, localY);
+ final float[] point = getTempPoint();
+ point[0] = x;
+ point[1] = y;
+ transformPointToViewLocal(point, child);
+ final boolean isInView = child.pointInView(point[0], point[1]);
if (isInView && outLocalPoint != null) {
- outLocalPoint.set(localX, localY);
+ outLocalPoint.set(point[0], point[1]);
}
return isInView;
}
/**
+ * @hide
+ */
+ public void transformPointToViewLocal(float[] point, View child) {
+ point[0] += mScrollX - child.mLeft;
+ point[1] += mScrollY - child.mTop;
+
+ if (!child.hasIdentityMatrix()) {
+ child.getInverseMatrix().mapPoints(point);
+ }
+ }
+
+ /**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
@@ -2495,15 +2470,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* Returns true if this ViewGroup should be considered as a single entity for removal
* when executing an Activity transition. If this is false, child elements will move
* individually during the transition.
+ *
* @return True if the ViewGroup should be acted on together during an Activity transition.
- * The default value is false when the background is null and true when the background
- * is not null or if {@link #getTransitionName()} is not null.
+ * The default value is true when there is a non-null background or if
+ * {@link #getTransitionName()} is not null or if a
+ * non-null {@link android.view.ViewOutlineProvider} other than
+ * {@link android.view.ViewOutlineProvider#BACKGROUND} was given to
+ * {@link #setOutlineProvider(ViewOutlineProvider)} and false otherwise.
*/
public boolean isTransitionGroup() {
if ((mGroupFlags & FLAG_IS_TRANSITION_GROUP_SET) != 0) {
return ((mGroupFlags & FLAG_IS_TRANSITION_GROUP) != 0);
} else {
- return getBackground() != null || getTransitionName() != null;
+ final ViewOutlineProvider outlineProvider = getOutlineProvider();
+ return getBackground() != null || getTransitionName() != null ||
+ (outlineProvider != null && outlineProvider != ViewOutlineProvider.BACKGROUND);
}
}
@@ -2758,6 +2739,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@Override
void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
+ if (getAccessibilityNodeProvider() != null) {
+ return;
+ }
if (mAttachInfo != null) {
final ArrayList<View> childrenForAccessibility = mAttachInfo.mTempArrayList;
childrenForAccessibility.clear();
@@ -2805,6 +2789,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* {@inheritDoc}
+ *
+ * <p>Subclasses should always call <code>super.onNestedPrePerformAccessibilityAction</code></p>
+ *
+ * @param target The target view dispatching this action
+ * @param action Action being performed; see
+ * {@link android.view.accessibility.AccessibilityNodeInfo}
+ * @param args Optional action arguments
+ * @return false by default. Subclasses should return true if they handle the event.
+ */
+ @Override
+ public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle args) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
*/
@Override
void dispatchDetachedFromWindow() {
@@ -3293,7 +3293,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* 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).
+ * sorted first by Z, then by child drawing order (if applicable). This list must be cleared
+ * after use to avoid leaking child Views.
*
* Uses a stable, insertion sort which is commonly O(n) for ViewGroups with very few elevated
* children.
@@ -3440,8 +3441,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * By default, children are clipped to the padding of the ViewGroup. This
- * allows view groups to override this behavior
+ * Sets whether this ViewGroup will clip its children to its padding, if
+ * padding is present.
+ * <p>
+ * By default, children are clipped to the padding of their parent
+ * Viewgroup. This clipping behavior is only enabled if padding is non-zero.
*
* @param clipToPadding true to clip children to the padding of the
* group, false otherwise
@@ -3455,7 +3459,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * Check if this ViewGroup is configured to clip child views to its padding.
+ * Returns whether this ViewGroup will clip its children to its padding, if
+ * padding is present.
+ * <p>
+ * By default, children are clipped to the padding of their parent
+ * Viewgroup. This clipping behavior is only enabled if padding is non-zero.
*
* @return true if this ViewGroup clips children to its padding, false otherwise
*
@@ -3505,6 +3513,44 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
+ /**
+ * Dispatches drawable hotspot changes to child views that meet at least
+ * one of the following criteria:
+ * <ul>
+ * <li>Returns {@code false} from both {@link View#isClickable()} and
+ * {@link View#isLongClickable()}</li>
+ * <li>Requests duplication of parent state via
+ * {@link View#setDuplicateParentStateEnabled(boolean)}</li>
+ * </ul>
+ *
+ * @param x hotspot x coordinate
+ * @param y hotspot y coordinate
+ * @see #drawableHotspotChanged(float, float)
+ */
+ @Override
+ public void dispatchDrawableHotspotChanged(float x, float y) {
+ final int count = mChildrenCount;
+ if (count == 0) {
+ return;
+ }
+
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ final View child = children[i];
+ // Children that are clickable on their own should not
+ // receive hotspots when their parent view does.
+ final boolean nonActionable = !child.isClickable() && !child.isLongClickable();
+ final boolean duplicatesState = (child.mViewFlags & DUPLICATE_PARENT_STATE) != 0;
+ if (nonActionable || duplicatesState) {
+ final float[] point = getTempPoint();
+ point[0] = x;
+ point[1] = y;
+ transformPointToViewLocal(point, child);
+ child.drawableHotspotChanged(point[0], point[1]);
+ }
+ }
+ }
+
@Override
void dispatchCancelPendingInputEvents() {
super.dispatchCancelPendingInputEvents();
@@ -3668,6 +3714,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @see #generateDefaultLayoutParams()
*/
public void addView(View child, int index) {
+ if (child == null) {
+ throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
+ }
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();
@@ -3725,6 +3774,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
System.out.println(this + " addView");
}
+ if (child == null) {
+ throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
+ }
+
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
@@ -3852,6 +3905,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
protected boolean addViewInLayout(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
+ if (child == null) {
+ throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
+ }
child.mParent = null;
addViewInner(child, index, params, preventRequestLayout);
child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
@@ -4065,9 +4121,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
*/
public void removeView(View view) {
- removeViewInternal(view);
- requestLayout();
- invalidate(true);
+ if (removeViewInternal(view)) {
+ requestLayout();
+ invalidate(true);
+ }
}
/**
@@ -4130,11 +4187,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
invalidate(true);
}
- private void removeViewInternal(View view) {
+ private boolean removeViewInternal(View view) {
final int index = indexOfChild(view);
if (index >= 0) {
removeViewInternal(index, view);
+ return true;
}
+ return false;
}
private void removeViewInternal(int index, View view) {
@@ -4149,9 +4208,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
clearChildFocus = true;
}
- if (view.isAccessibilityFocused()) {
- view.clearAccessibilityFocus();
- }
+ view.clearAccessibilityFocus();
cancelTouchTarget(view);
cancelHoverTarget(view);
@@ -4244,9 +4301,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
clearChildFocus = true;
}
- if (view.isAccessibilityFocused()) {
- view.clearAccessibilityFocus();
- }
+ view.clearAccessibilityFocus();
cancelTouchTarget(view);
cancelHoverTarget(view);
@@ -4331,9 +4386,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
clearChildFocus = true;
}
- if (view.isAccessibilityFocused()) {
- view.clearAccessibilityFocus();
- }
+ view.clearAccessibilityFocus();
cancelTouchTarget(view);
cancelHoverTarget(view);
@@ -4922,11 +4975,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
rect.set(r);
if (!child.hasIdentityMatrix()) {
- child.getMatrix().mapRect(rect);
+ child.getMatrix().mapRect(rect);
}
- int dx = child.mLeft - mScrollX;
- int dy = child.mTop - mScrollY;
+ final int dx = child.mLeft - mScrollX;
+ final int dy = child.mTop - mScrollY;
rect.offset(dx, dy);
@@ -4944,14 +4997,33 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
offset.y += dy;
}
- if (rect.intersect(0, 0, mRight - mLeft, mBottom - mTop)) {
- if (mParent == null) return true;
- r.set((int) (rect.left + 0.5f), (int) (rect.top + 0.5f),
- (int) (rect.right + 0.5f), (int) (rect.bottom + 0.5f));
- return mParent.getChildVisibleRect(this, r, offset);
+ final int width = mRight - mLeft;
+ final int height = mBottom - mTop;
+
+ boolean rectIsVisible = true;
+ if (mParent == null ||
+ (mParent instanceof ViewGroup && ((ViewGroup) mParent).getClipChildren())) {
+ // Clip to bounds.
+ rectIsVisible = rect.intersect(0, 0, width, height);
}
- return false;
+ if (rectIsVisible && (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
+ // Clip to padding.
+ rectIsVisible = rect.intersect(mPaddingLeft, mPaddingTop,
+ width - mPaddingRight, height - mPaddingBottom);
+ }
+
+ if (rectIsVisible && mClipBounds != null) {
+ // Clip to clipBounds.
+ rectIsVisible = rect.intersect(mClipBounds.left, mClipBounds.top, mClipBounds.right,
+ mClipBounds.bottom);
+ }
+ r.set((int) (rect.left + 0.5f), (int) (rect.top + 0.5f), (int) (rect.right + 0.5f),
+ (int) (rect.bottom + 0.5f));
+ if (rectIsVisible && mParent != null) {
+ rectIsVisible = mParent.getChildVisibleRect(this, r, offset);
+ }
+ return rectIsVisible;
}
/**
@@ -5835,28 +5907,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
@Override
- public void drawableHotspotChanged(float x, float y) {
- super.drawableHotspotChanged(x, y);
-
- if ((mGroupFlags & FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE) != 0) {
- if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0) {
- throw new IllegalStateException("addStateFromChildren cannot be enabled if a"
- + " child has duplicateParentState set to true");
- }
-
- final View[] children = mChildren;
- final int count = mChildrenCount;
-
- for (int i = 0; i < count; i++) {
- final View child = children[i];
- if ((child.mViewFlags & DUPLICATE_PARENT_STATE) != 0) {
- child.drawableHotspotChanged(x, y);
- }
- }
- }
- }
-
- @Override
protected int[] onCreateDrawableState(int extraSpace) {
if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) == 0) {
return super.onCreateDrawableState(extraSpace);
@@ -6033,7 +6083,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
- if (child.isLayoutDirectionInherited()) {
+ if (child.isLayoutDirectionInherited() && !child.isPaddingResolved()) {
child.resolvePadding();
}
}
@@ -6048,7 +6098,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
- if (child.isLayoutDirectionInherited()) {
+ if (child.isLayoutDirectionInherited() && !child.areDrawablesResolved()) {
child.resolveDrawables();
}
}
@@ -6488,7 +6538,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
public static class MarginLayoutParams extends ViewGroup.LayoutParams {
/**
- * The left margin in pixels of the child.
+ * The left margin in pixels of the child. Margin values should be positive.
* Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
* to this field.
*/
@@ -6496,7 +6546,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
public int leftMargin;
/**
- * The top margin in pixels of the child.
+ * The top margin in pixels of the child. Margin values should be positive.
* Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
* to this field.
*/
@@ -6504,7 +6554,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
public int topMargin;
/**
- * The right margin in pixels of the child.
+ * The right margin in pixels of the child. Margin values should be positive.
* Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
* to this field.
*/
@@ -6512,7 +6562,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
public int rightMargin;
/**
- * The bottom margin in pixels of the child.
+ * The bottom margin in pixels of the child. Margin values should be positive.
* Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
* to this field.
*/
@@ -6520,7 +6570,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
public int bottomMargin;
/**
- * The start margin in pixels of the child.
+ * The start margin in pixels of the child. Margin values should be positive.
* Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
* to this field.
*/
@@ -6528,7 +6578,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private int startMargin = DEFAULT_MARGIN_RELATIVE;
/**
- * The end margin in pixels of the child.
+ * The end margin in pixels of the child. Margin values should be positive.
* Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
* to this field.
*/
@@ -6709,6 +6759,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* Sets the margins, in pixels. A call to {@link android.view.View#requestLayout()} needs
* to be done so that the new margins are taken into account. Left and right margins may be
* overriden by {@link android.view.View#requestLayout()} depending on layout direction.
+ * Margin values should be positive.
*
* @param left the left margin size
* @param top the top margin size
@@ -6738,7 +6789,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* Sets the relative margins, in pixels. A call to {@link android.view.View#requestLayout()}
* needs to be done so that the new relative margins are taken into account. Left and right
* margins may be overriden by {@link android.view.View#requestLayout()} depending on layout
- * direction.
+ * direction. Margin values should be positive.
*
* @param start the start margin size
* @param top the top margin size
@@ -6761,7 +6812,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * Sets the relative start margin.
+ * Sets the relative start margin. Margin values should be positive.
*
* @param start the start margin size
*
@@ -6794,7 +6845,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * Sets the relative end margin.
+ * Sets the relative end margin. Margin values should be positive.
*
* @param end the end margin size
*
diff --git a/core/java/android/view/ViewOverlay.java b/core/java/android/view/ViewOverlay.java
index 0cf9ddd..5e5ef29 100644
--- a/core/java/android/view/ViewOverlay.java
+++ b/core/java/android/view/ViewOverlay.java
@@ -130,8 +130,11 @@ public class ViewOverlay {
super(context);
mHostView = hostView;
mAttachInfo = mHostView.mAttachInfo;
+
mRight = hostView.getWidth();
mBottom = hostView.getHeight();
+ // pass right+bottom directly to RenderNode, since not going through setters
+ mRenderNode.setLeftTopRightBottom(0, 0, mRight, mBottom);
}
public void add(Drawable drawable) {
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 87a37f4..035871d 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -17,6 +17,7 @@
package android.view;
import android.graphics.Rect;
+import android.os.Bundle;
import android.view.accessibility.AccessibilityEvent;
/**
@@ -551,4 +552,23 @@ public interface ViewParent {
* @return true if this parent consumed the fling ahead of the target view
*/
public boolean onNestedPreFling(View target, float velocityX, float velocityY);
+
+ /**
+ * React to an accessibility action delegated by a target descendant view before the target
+ * processes it.
+ *
+ * <p>This method may be called by a target descendant view if the target wishes to give
+ * a view in its parent chain a chance to react to the event before normal processing occurs.
+ * Most commonly this will be a scroll event such as
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTION_SCROLL_FORWARD}.
+ * A ViewParent that supports acting as a nested scrolling parent should override this
+ * method and act accordingly to implement scrolling via accesibility systems.</p>
+ *
+ * @param target The target view dispatching this action
+ * @param action Action being performed; see
+ * {@link android.view.accessibility.AccessibilityNodeInfo}
+ * @param arguments Optional action arguments
+ * @return true if the action was consumed by this ViewParent
+ */
+ public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle arguments);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 81fc966..e4d82b1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -245,7 +245,7 @@ public final class ViewRootImpl implements ViewParent,
// These can be accessed by any thread, must be protected with a lock.
// Surface can never be reassigned or cleared (use Surface.clear()).
- private final Surface mSurface = new Surface();
+ final Surface mSurface = new Surface();
boolean mAdded;
boolean mAddedTouchMode;
@@ -526,7 +526,7 @@ public final class ViewRootImpl implements ViewParent,
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
- mAttachInfo.mContentInsets, mInputChannel);
+ mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
@@ -560,39 +560,43 @@ public final class ViewRootImpl implements ViewParent,
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
- "Unable to add window -- token " + attrs.token
- + " is not valid; is your activity running?");
+ "Unable to add window -- token " + attrs.token
+ + " is not valid; is your activity running?");
case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
throw new WindowManager.BadTokenException(
- "Unable to add window -- token " + attrs.token
- + " is not for an application");
+ "Unable to add window -- token " + attrs.token
+ + " is not for an application");
case WindowManagerGlobal.ADD_APP_EXITING:
throw new WindowManager.BadTokenException(
- "Unable to add window -- app for token " + attrs.token
- + " is exiting");
+ "Unable to add window -- app for token " + attrs.token
+ + " is exiting");
case WindowManagerGlobal.ADD_DUPLICATE_ADD:
throw new WindowManager.BadTokenException(
- "Unable to add window -- window " + mWindow
- + " has already been added");
+ "Unable to add window -- window " + mWindow
+ + " has already been added");
case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
// Silently ignore -- we would have just removed it
// right away, anyway.
return;
case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
throw new WindowManager.BadTokenException(
- "Unable to add window " + mWindow +
- " -- another window of this type already exists");
+ "Unable to add window " + mWindow +
+ " -- another window of this type already exists");
case WindowManagerGlobal.ADD_PERMISSION_DENIED:
throw new WindowManager.BadTokenException(
- "Unable to add window " + mWindow +
- " -- permission denied for this window type");
+ "Unable to add window " + mWindow +
+ " -- permission denied for this window type");
case WindowManagerGlobal.ADD_INVALID_DISPLAY:
throw new WindowManager.InvalidDisplayException(
- "Unable to add window " + mWindow +
- " -- the specified display can not be found");
+ "Unable to add window " + mWindow +
+ " -- the specified display can not be found");
+ case WindowManagerGlobal.ADD_INVALID_TYPE:
+ throw new WindowManager.InvalidDisplayException(
+ "Unable to add window " + mWindow
+ + " -- the specified window type is not valid");
}
throw new RuntimeException(
- "Unable to add window -- unknown error code " + res);
+ "Unable to add window -- unknown error code " + res);
}
if (view instanceof RootViewSurfaceTaker) {
@@ -727,7 +731,10 @@ public final class ViewRootImpl implements ViewParent,
mAttachInfo.mHardwareRenderer.destroy();
}
- final boolean translucent = attrs.format != PixelFormat.OPAQUE;
+ final Rect insets = attrs.surfaceInsets;
+ final boolean hasSurfaceInsets = insets.left != 0 || insets.right != 0
+ || insets.top != 0 || insets.bottom != 0;
+ final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets;
mAttachInfo.mHardwareRenderer = HardwareRenderer.create(mContext, translucent);
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString());
@@ -1490,7 +1497,11 @@ public final class ViewRootImpl implements ViewParent,
// relayoutWindow may decide to destroy mSurface. As that decision
// happens in WindowManager service, we need to be defensive here
// and stop using the surface in case it gets destroyed.
- mAttachInfo.mHardwareRenderer.pauseSurface(mSurface);
+ if (mAttachInfo.mHardwareRenderer.pauseSurface(mSurface)) {
+ // Animations were running so we need to push a frame
+ // to resume them
+ mDirty.set(0, 0, mWidth, mHeight);
+ }
}
final int surfaceGenerationId = mSurface.getGenerationId();
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
@@ -1646,6 +1657,9 @@ public final class ViewRootImpl implements ViewParent,
mLastScrolledFocus.clear();
}
mScrollY = mCurScrollY = 0;
+ if (mView instanceof RootViewSurfaceTaker) {
+ ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
+ }
if (mScroller != null) {
mScroller.abortAnimation();
}
@@ -1740,8 +1754,8 @@ public final class ViewRootImpl implements ViewParent,
if (hwInitialized ||
mWidth != mAttachInfo.mHardwareRenderer.getWidth() ||
mHeight != mAttachInfo.mHardwareRenderer.getHeight()) {
- final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
- mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, surfaceInsets);
+ mAttachInfo.mHardwareRenderer.setup(
+ mWidth, mHeight, mWindowAttributes.surfaceInsets);
if (!hwInitialized) {
mAttachInfo.mHardwareRenderer.invalidate(mSurface);
mFullRedrawNeeded = true;
@@ -2255,6 +2269,7 @@ public final class ViewRootImpl implements ViewParent,
canvas.drawHardwareLayer(mResizeBuffer, mHardwareXOffset, mHardwareYOffset,
mResizePaint);
}
+ drawAccessibilityFocusedDrawableIfNeeded(canvas);
}
/**
@@ -2415,6 +2430,9 @@ public final class ViewRootImpl implements ViewParent,
if (mCurScrollY != curScrollY) {
mCurScrollY = curScrollY;
fullRedrawNeeded = true;
+ if (mView instanceof RootViewSurfaceTaker) {
+ ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
+ }
}
final float appScale = mAttachInfo.mApplicationScale;
@@ -2474,18 +2492,38 @@ public final class ViewRootImpl implements ViewParent,
dirty.offset(surfaceInsets.left, surfaceInsets.right);
}
- if (!dirty.isEmpty() || mIsAnimating) {
+ boolean accessibilityFocusDirty = false;
+ final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
+ if (drawable != null) {
+ final Rect bounds = mAttachInfo.mTmpInvalRect;
+ final boolean hasFocus = getAccessibilityFocusedRect(bounds);
+ if (!hasFocus) {
+ bounds.setEmpty();
+ }
+ if (!bounds.equals(drawable.getBounds())) {
+ accessibilityFocusDirty = true;
+ }
+ }
+
+ if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
+ // If accessibility focus moved, always invalidate the root.
+ boolean invalidateRoot = accessibilityFocusDirty;
+
// Draw with hardware renderer.
mIsAnimating = false;
- boolean invalidateRoot = false;
+
if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
mHardwareYOffset = yOffset;
mHardwareXOffset = xOffset;
- mAttachInfo.mHardwareRenderer.invalidateRoot();
+ invalidateRoot = true;
}
mResizeAlpha = resizeAlpha;
+ if (invalidateRoot) {
+ mAttachInfo.mHardwareRenderer.invalidateRoot();
+ }
+
dirty.setEmpty();
mBlockResizeBuffer = false;
@@ -2604,6 +2642,8 @@ public final class ViewRootImpl implements ViewParent,
attachInfo.mSetIgnoreDirtyState = false;
mView.draw(canvas);
+
+ drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
@@ -2627,7 +2667,56 @@ public final class ViewRootImpl implements ViewParent,
return true;
}
- Drawable getAccessibilityFocusedDrawable() {
+ /**
+ * We want to draw a highlight around the current accessibility focused.
+ * Since adding a style for all possible view is not a viable option we
+ * have this specialized drawing method.
+ *
+ * Note: We are doing this here to be able to draw the highlight for
+ * virtual views in addition to real ones.
+ *
+ * @param canvas The canvas on which to draw.
+ */
+ private void drawAccessibilityFocusedDrawableIfNeeded(Canvas canvas) {
+ final Rect bounds = mAttachInfo.mTmpInvalRect;
+ if (getAccessibilityFocusedRect(bounds)) {
+ final Drawable drawable = getAccessibilityFocusedDrawable();
+ if (drawable != null) {
+ drawable.setBounds(bounds);
+ drawable.draw(canvas);
+ }
+ } else if (mAttachInfo.mAccessibilityFocusDrawable != null) {
+ mAttachInfo.mAccessibilityFocusDrawable.setBounds(0, 0, 0, 0);
+ }
+ }
+
+ private boolean getAccessibilityFocusedRect(Rect bounds) {
+ final AccessibilityManager manager = AccessibilityManager.getInstance(mView.mContext);
+ if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) {
+ return false;
+ }
+
+ final View host = mAccessibilityFocusedHost;
+ if (host == null || host.mAttachInfo == null) {
+ return false;
+ }
+
+ final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
+ if (provider == null) {
+ host.getBoundsOnScreen(bounds, true);
+ } else if (mAccessibilityFocusedVirtualView != null) {
+ mAccessibilityFocusedVirtualView.getBoundsInScreen(bounds);
+ } else {
+ return false;
+ }
+
+ final AttachInfo attachInfo = mAttachInfo;
+ bounds.offset(-attachInfo.mWindowLeft, -attachInfo.mWindowTop);
+ bounds.intersect(0, 0, attachInfo.mViewRootImpl.mWidth, attachInfo.mViewRootImpl.mHeight);
+ return !bounds.isEmpty();
+ }
+
+ private Drawable getAccessibilityFocusedDrawable() {
// Lazily load the accessibility focus drawable.
if (mAttachInfo.mAccessibilityFocusDrawable == null) {
final TypedValue value = new TypedValue();
@@ -3014,6 +3103,7 @@ public final class ViewRootImpl implements ViewParent,
private final static int MSG_INVALIDATE_WORLD = 23;
private final static int MSG_WINDOW_MOVED = 24;
private final static int MSG_SYNTHESIZE_INPUT_EVENT = 25;
+ private final static int MSG_DISPATCH_WINDOW_SHOWN = 26;
final class ViewRootHandler extends Handler {
@Override
@@ -3063,6 +3153,8 @@ public final class ViewRootImpl implements ViewParent,
return "MSG_WINDOW_MOVED";
case MSG_SYNTHESIZE_INPUT_EVENT:
return "MSG_SYNTHESIZE_INPUT_EVENT";
+ case MSG_DISPATCH_WINDOW_SHOWN:
+ return "MSG_DISPATCH_WINDOW_SHOWN";
}
return super.getMessageName(message);
}
@@ -3291,6 +3383,9 @@ public final class ViewRootImpl implements ViewParent,
invalidateWorld(mView);
}
} break;
+ case MSG_DISPATCH_WINDOW_SHOWN: {
+ handleDispatchWindowShown();
+ }
}
}
}
@@ -3509,12 +3604,19 @@ public final class ViewRootImpl implements ViewParent,
if (mView == null || !mAdded) {
Slog.w(TAG, "Dropping event due to root view being removed: " + q.mEvent);
return true;
- } else if (!mAttachInfo.mHasWindowFocus &&
- !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) &&
- !isTerminalInputEvent(q.mEvent)) {
- // If this is a focused event and the window doesn't currently have input focus,
- // then drop this event. This could be an event that came back from the previous
- // stage but the window has lost focus in the meantime.
+ } else if ((!mAttachInfo.mHasWindowFocus || mStopped)
+ && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
+ // This is a focus event and the window doesn't currently have input focus or
+ // has stopped. This could be an event that came back from the previous stage
+ // but the window has lost focus or stopped in the meantime.
+ if (isTerminalInputEvent(q.mEvent)) {
+ // Don't drop terminal input events, however mark them as canceled.
+ q.mEvent.cancel();
+ Slog.w(TAG, "Cancelling event due to no window focus: " + q.mEvent);
+ return false;
+ }
+
+ // Drop non-terminal input events.
Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent);
return true;
}
@@ -5137,6 +5239,10 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ public void handleDispatchWindowShown() {
+ mAttachInfo.mTreeObserver.dispatchOnWindowShown();
+ }
+
public void getLastTouchPoint(Point outLocation) {
outLocation.x = (int) mLastTouchPoint.x;
outLocation.y = (int) mLastTouchPoint.y;
@@ -5997,6 +6103,10 @@ public final class ViewRootImpl implements ViewParent,
mHandler.sendMessage(msg);
}
+ public void dispatchWindowShown() {
+ mHandler.sendEmptyMessage(MSG_DISPATCH_WINDOW_SHOWN);
+ }
+
public void dispatchCloseSystemDialogs(String reason) {
Message msg = Message.obtain();
msg.what = MSG_CLOSE_SYSTEM_DIALOGS;
@@ -6307,6 +6417,11 @@ public final class ViewRootImpl implements ViewParent,
return false;
}
+ @Override
+ public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle args) {
+ return false;
+ }
+
void changeCanvasOpacity(boolean opaque) {
Log.d(TAG, "changeCanvasOpacity: opaque=" + opaque);
if (mAttachInfo.mHardwareRenderer != null) {
@@ -6507,6 +6622,14 @@ public final class ViewRootImpl implements ViewParent,
viewAncestor.dispatchDoneAnimating();
}
}
+
+ @Override
+ public void dispatchWindowShown() {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchWindowShown();
+ }
+ }
}
public static final class CalledFromWrongThreadException extends AndroidRuntimeException {
@@ -6712,26 +6835,6 @@ public final class ViewRootImpl implements ViewParent,
}
@Override
- public void computeClickPointInScreen(long accessibilityNodeId, Region interactiveRegion,
- int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
- ViewRootImpl viewRootImpl = mViewRootImpl.get();
- if (viewRootImpl != null && viewRootImpl.mView != null) {
- viewRootImpl.getAccessibilityInteractionController()
- .computeClickPointInScreenClientThread(accessibilityNodeId,
- interactiveRegion, interactionId, callback, interrogatingPid,
- interrogatingTid, spec);
- } else {
- // We cannot make the call and notify the caller so it does not wait.
- try {
- callback.setComputeClickPointInScreenActionResult(null, interactionId);
- } catch (RemoteException re) {
- /* best effort - ignore */
- }
- }
- }
-
- @Override
public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId,
String viewId, Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index a9444b4..521fd31 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -37,6 +37,8 @@ public final class ViewTreeObserver {
private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners;
private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
+ private CopyOnWriteArrayList<OnEnterAnimationCompleteListener>
+ mOnEnterAnimationCompleteListeners;
// Non-recursive listeners use CopyOnWriteArray
// Any listener invoked from ViewRootImpl.performTraversals() should not be recursive
@@ -44,10 +46,15 @@ public final class ViewTreeObserver {
private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners;
private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners;
+ private CopyOnWriteArray<OnWindowShownListener> mOnWindowShownListeners;
// These listeners cannot be mutated during dispatch
private ArrayList<OnDrawListener> mOnDrawListeners;
+ /** Remains false until #dispatchOnWindowShown() is called. If a listener registers after
+ * that the listener will be immediately called. */
+ private boolean mWindowShown;
+
private boolean mAlive = true;
/**
@@ -174,6 +181,19 @@ public final class ViewTreeObserver {
}
/**
+ * Interface definition for a callback noting when a system window has been displayed.
+ * This is only used for non-Activity windows. Activity windows can use
+ * Activity.onEnterAnimationComplete() to get the same signal.
+ * @hide
+ */
+ public interface OnWindowShownListener {
+ /**
+ * Callback method to be invoked when a non-activity window is fully shown.
+ */
+ void onWindowShown();
+ }
+
+ /**
* Parameters used with OnComputeInternalInsetsListener.
*
* We are not yet ready to commit to this API and support it, so
@@ -298,6 +318,13 @@ public final class ViewTreeObserver {
}
/**
+ * @hide
+ */
+ public interface OnEnterAnimationCompleteListener {
+ public void onEnterAnimationComplete();
+ }
+
+ /**
* Creates a new ViewTreeObserver. This constructor should not be called
*/
ViewTreeObserver() {
@@ -375,6 +402,14 @@ public final class ViewTreeObserver {
}
}
+ if (observer.mOnWindowShownListeners != null) {
+ if (mOnWindowShownListeners != null) {
+ mOnWindowShownListeners.addAll(observer.mOnWindowShownListeners);
+ } else {
+ mOnWindowShownListeners = observer.mOnWindowShownListeners;
+ }
+ }
+
observer.kill();
}
@@ -568,6 +603,45 @@ public final class ViewTreeObserver {
}
/**
+ * Register a callback to be invoked when the view tree window has been shown
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ * @hide
+ */
+ public void addOnWindowShownListener(OnWindowShownListener listener) {
+ checkIsAlive();
+
+ if (mOnWindowShownListeners == null) {
+ mOnWindowShownListeners = new CopyOnWriteArray<OnWindowShownListener>();
+ }
+
+ mOnWindowShownListeners.add(listener);
+ if (mWindowShown) {
+ listener.onWindowShown();
+ }
+ }
+
+ /**
+ * Remove a previously installed window shown callback
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnWindowShownListener(OnWindowShownListener)
+ * @hide
+ */
+ public void removeOnWindowShownListener(OnWindowShownListener victim) {
+ checkIsAlive();
+ if (mOnWindowShownListeners == null) {
+ return;
+ }
+ mOnWindowShownListeners.remove(victim);
+ }
+
+ /**
* <p>Register a callback to be invoked when the view tree is about to be drawn.</p>
* <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
* {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
@@ -715,6 +789,29 @@ public final class ViewTreeObserver {
mOnComputeInternalInsetsListeners.remove(victim);
}
+ /**
+ * @hide
+ */
+ public void addOnEnterAnimationCompleteListener(OnEnterAnimationCompleteListener listener) {
+ checkIsAlive();
+ if (mOnEnterAnimationCompleteListeners == null) {
+ mOnEnterAnimationCompleteListeners =
+ new CopyOnWriteArrayList<OnEnterAnimationCompleteListener>();
+ }
+ mOnEnterAnimationCompleteListeners.add(listener);
+ }
+
+ /**
+ * @hide
+ */
+ public void removeOnEnterAnimationCompleteListener(OnEnterAnimationCompleteListener listener) {
+ checkIsAlive();
+ if (mOnEnterAnimationCompleteListeners == null) {
+ return;
+ }
+ mOnEnterAnimationCompleteListeners.remove(listener);
+ }
+
private void checkIsAlive() {
if (!mAlive) {
throw new IllegalStateException("This ViewTreeObserver is not alive, call "
@@ -854,6 +951,27 @@ public final class ViewTreeObserver {
}
/**
+ * Notifies registered listeners that the window is now shown
+ * @hide
+ */
+ @SuppressWarnings("unchecked")
+ public final void dispatchOnWindowShown() {
+ mWindowShown = true;
+ final CopyOnWriteArray<OnWindowShownListener> listeners = mOnWindowShownListeners;
+ if (listeners != null && listeners.size() > 0) {
+ CopyOnWriteArray.Access<OnWindowShownListener> access = listeners.start();
+ try {
+ int count = access.size();
+ for (int i = 0; i < count; i++) {
+ access.get(i).onWindowShown();
+ }
+ } finally {
+ listeners.end();
+ }
+ }
+ }
+
+ /**
* Notifies registered listeners that the drawing pass is about to start.
*/
public final void dispatchOnDraw() {
@@ -936,6 +1054,23 @@ public final class ViewTreeObserver {
}
/**
+ * @hide
+ */
+ public final void dispatchOnEnterAnimationComplete() {
+ // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+ // perform the dispatching. The iterator is a safe guard against listeners that
+ // could mutate the list by calling the various add/remove methods. This prevents
+ // the array from being modified while we iterate it.
+ final CopyOnWriteArrayList<OnEnterAnimationCompleteListener> listeners =
+ mOnEnterAnimationCompleteListeners;
+ if (listeners != null && !listeners.isEmpty()) {
+ for (OnEnterAnimationCompleteListener listener : listeners) {
+ listener.onEnterAnimationComplete();
+ }
+ }
+ }
+
+ /**
* Copy on write array. This array is not thread safe, and only one loop can
* iterate over this array at any given time. This class avoids allocations
* until a concurrent modification happens.
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 63ab7d2..8704356 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -18,13 +18,14 @@ package android.view;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.media.session.MediaController;
-import android.media.session.MediaSession;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
@@ -101,10 +102,12 @@ public abstract class Window {
*/
public static final int FEATURE_SWIPE_TO_DISMISS = 11;
/**
- * Flag for requesting that window content changes should be represented
- * with scenes and transitions.
+ * Flag for requesting that window content changes should be animated using a
+ * TransitionManager.
*
- * TODO Add docs
+ * <p>The TransitionManager is set using
+ * {@link #setTransitionManager(android.transition.TransitionManager)}. If none is set,
+ * a default TransitionManager will be used.</p>
*
* @see #setContentView
*/
@@ -155,7 +158,11 @@ public abstract class Window {
public static final String NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME =
"android:navigation:background";
- /** The default features enabled */
+ /**
+ * The default features enabled.
+ * @deprecated use {@link #getDefaultFeatures(android.content.Context)} instead.
+ */
+ @Deprecated
@SuppressWarnings({"PointlessBitwiseExpression"})
protected static final int DEFAULT_FEATURES = (1 << FEATURE_OPTIONS_PANEL) |
(1 << FEATURE_CONTEXT_MENU);
@@ -184,8 +191,8 @@ public abstract class Window {
private boolean mSetCloseOnTouchOutside = false;
private int mForcedWindowFlags = 0;
- private int mFeatures = DEFAULT_FEATURES;
- private int mLocalFeatures = DEFAULT_FEATURES;
+ private int mFeatures;
+ private int mLocalFeatures;
private boolean mHaveWindowFormat = false;
private boolean mHaveDimAmount = false;
@@ -363,7 +370,7 @@ public abstract class Window {
/**
* This hook is called whenever the window focus changes. See
* {@link View#onWindowFocusChanged(boolean)
- * View.onWindowFocusChanged(boolean)} for more information.
+ * View.onWindowFocusChangedNotLocked(boolean)} for more information.
*
* @param hasFocus Whether the window now has focus.
*/
@@ -443,6 +450,7 @@ public abstract class Window {
public Window(Context context) {
mContext = context;
+ mFeatures = mLocalFeatures = getDefaultFeatures(context);
}
/**
@@ -801,9 +809,6 @@ public abstract class Window {
public void setFlags(int flags, int mask) {
final WindowManager.LayoutParams attrs = getAttributes();
attrs.flags = (attrs.flags&~mask) | (flags&mask);
- if ((mask&WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY) != 0) {
- attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY;
- }
mForcedWindowFlags |= mask;
dispatchWindowAttributesChanged(attrs);
}
@@ -817,6 +822,15 @@ public abstract class Window {
/**
* {@hide}
*/
+ protected void setNeedsMenuKey(int value) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.needsMenuKey = value;
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * {@hide}
+ */
protected void dispatchWindowAttributesChanged(WindowManager.LayoutParams attrs) {
if (mCallback != null) {
mCallback.onWindowAttributesChanged(attrs);
@@ -893,6 +907,14 @@ public abstract class Window {
}
/** @hide */
+ @SystemApi
+ public void setDisableWallpaperTouchEvents(boolean disable) {
+ setPrivateFlags(disable
+ ? WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS : 0,
+ WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS);
+ }
+
+ /** @hide */
public abstract void alwaysReadCloseOnTouchAttr();
/** @hide */
@@ -999,10 +1021,16 @@ public abstract class Window {
* <p>Note that calling this function "locks in" various characteristics
* of the window that can not, from this point forward, be changed: the
* features that have been requested with {@link #requestFeature(int)},
- * and certain window flags as described in {@link #setFlags(int, int)}.
+ * and certain window flags as described in {@link #setFlags(int, int)}.</p>
+ *
+ * <p>If {@link #FEATURE_CONTENT_TRANSITIONS} is set, the window's
+ * TransitionManager will be used to animate content from the current
+ * content View to view.</p>
*
* @param view The desired content to display.
* @param params Layout parameters for the view.
+ * @see #getTransitionManager()
+ * @see #setTransitionManager(android.transition.TransitionManager)
*/
public abstract void setContentView(View view, ViewGroup.LayoutParams params);
@@ -1069,17 +1097,40 @@ public abstract class Window {
public abstract void onConfigurationChanged(Configuration newConfig);
/**
+ * Sets the window elevation.
+ * <p>
+ * Changes to this property take effect immediately and will cause the
+ * window surface to be recreated. This is an expensive operation and as a
+ * result, this property should not be animated.
+ *
+ * @param elevation The window elevation.
+ * @see View#setElevation(float)
+ * @see android.R.styleable#Window_windowElevation
+ */
+ public void setElevation(float elevation) {}
+
+ /**
+ * Sets whether window content should be clipped to the outline of the
+ * window background.
+ *
+ * @param clipToOutline Whether window content should be clipped to the
+ * outline of the window background.
+ * @see View#setClipToOutline(boolean)
+ * @see android.R.styleable#Window_windowClipToOutline
+ */
+ public void setClipToOutline(boolean clipToOutline) {}
+
+ /**
* Change the background of this window to a Drawable resource. Setting the
* background to null will make the window be opaque. To make the window
* transparent, you can use an empty drawable (for instance a ColorDrawable
* with the color 0 or the system drawable android:drawable/empty.)
*
- * @param resid The resource identifier of a drawable resource which will be
- * installed as the new background.
+ * @param resId The resource identifier of a drawable resource which will
+ * be installed as the new background.
*/
- public void setBackgroundDrawableResource(int resid)
- {
- setBackgroundDrawable(mContext.getDrawable(resid));
+ public void setBackgroundDrawableResource(int resId) {
+ setBackgroundDrawable(mContext.getDrawable(resId));
}
/**
@@ -1125,31 +1176,31 @@ public abstract class Window {
* Set an explicit Drawable value for feature of this window. You must
* have called requestFeature(featureId) before calling this function.
*
- * @param featureId The desired drawable feature to change.
- * Features are constants defined by Window.
+ * @param featureId The desired drawable feature to change. Features are
+ * constants defined by Window.
* @param drawable A Drawable object to display.
*/
public abstract void setFeatureDrawable(int featureId, Drawable drawable);
/**
- * Set a custom alpha value for the given drawale feature, controlling how
+ * Set a custom alpha value for the given drawable feature, controlling how
* much the background is visible through it.
*
- * @param featureId The desired drawable feature to change.
- * Features are constants defined by Window.
+ * @param featureId The desired drawable feature to change. Features are
+ * constants defined by Window.
* @param alpha The alpha amount, 0 is completely transparent and 255 is
* completely opaque.
*/
public abstract void setFeatureDrawableAlpha(int featureId, int alpha);
/**
- * Set the integer value for a feature. The range of the value depends on
- * the feature being set. For FEATURE_PROGRESSS, it should go from 0 to
- * 10000. At 10000 the progress is complete and the indicator hidden.
+ * Set the integer value for a feature. The range of the value depends on
+ * the feature being set. For {@link #FEATURE_PROGRESS}, it should go from
+ * 0 to 10000. At 10000 the progress is complete and the indicator hidden.
*
- * @param featureId The desired feature to change.
- * Features are constants defined by Window.
- * @param value The value for the feature. The interpretation of this
+ * @param featureId The desired feature to change. Features are constants
+ * defined by Window.
+ * @param value The value for the feature. The interpretation of this
* value is feature-specific.
*/
public abstract void setFeatureInt(int featureId, int value);
@@ -1246,6 +1297,25 @@ public abstract class Window {
}
/**
+ * Return the feature bits set by default on a window.
+ * @param context The context used to access resources
+ */
+ public static int getDefaultFeatures(Context context) {
+ int features = 0;
+
+ final Resources res = context.getResources();
+ if (res.getBoolean(com.android.internal.R.bool.config_defaultWindowFeatureOptionsPanel)) {
+ features |= 1 << FEATURE_OPTIONS_PANEL;
+ }
+
+ if (res.getBoolean(com.android.internal.R.bool.config_defaultWindowFeatureContextMenu)) {
+ features |= 1 << FEATURE_CONTEXT_MENU;
+ }
+
+ return features;
+ }
+
+ /**
* Query for the availability of a certain feature.
*
* @param feature The feature ID to check
@@ -1405,6 +1475,7 @@ public abstract class Window {
* {@link #setContentView}) if {@link #FEATURE_CONTENT_TRANSITIONS} has been granted.</p>
*
* @return This window's content TransitionManager or null if none is set.
+ * @attr ref android.R.styleable#Window_windowContentTransitionManager
*/
public TransitionManager getTransitionManager() {
return null;
@@ -1415,6 +1486,7 @@ public abstract class Window {
* Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
*
* @param tm The TransitionManager to use for scene changes.
+ * @attr ref android.R.styleable#Window_windowContentTransitionManager
*/
public void setTransitionManager(TransitionManager tm) {
throw new UnsupportedOperationException();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 47ee52e..84434f7 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.SystemApi;
import android.app.Presentation;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -523,15 +524,6 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;
/**
- * Window type: Recents. Same layer as {@link #TYPE_SYSTEM_DIALOG} but only appears on
- * one user's screen.
- * In multiuser systems shows on all users' windows.
- * @hide
- */
- public static final int TYPE_RECENTS_OVERLAY = FIRST_SYSTEM_WINDOW+28;
-
-
- /**
* Window type: keyguard scrim window. Shows if keyguard needs to be restarted.
* In multiuser systems shows on all users' windows.
* @hide
@@ -551,6 +543,19 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;
/**
+ * Window type: Windows that are overlaid <em>only</em> by an {@link
+ * android.accessibilityservice.AccessibilityService} for interception of
+ * user interactions without changing the windows an accessibility service
+ * can introspect. In particular, an accessibility service can introspect
+ * only windows that a sighted user can interact with which is they can touch
+ * these windows or can type into these windows. For example, if there
+ * is a full screen accessibility overlay that is touchable, the windows
+ * below it will be introspectable by an accessibility service regardless
+ * they are covered by a touchable window.
+ */
+ public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;
+
+ /**
* End of types of system windows.
*/
public static final int LAST_SYSTEM_WINDOW = 2999;
@@ -887,9 +892,6 @@ public interface WindowManager extends ViewManager {
*/
public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;
- // ----- HIDDEN FLAGS.
- // These start at the high bit and go down.
-
/**
* Flag for a window in local focus mode.
* Window in local focus mode can control focus independent of window manager using
@@ -912,17 +914,12 @@ public interface WindowManager extends ViewManager {
public static final int FLAG_SLIPPERY = 0x20000000;
/**
- * Flag for a window belonging to an activity that responds to {@link KeyEvent#KEYCODE_MENU}
- * and therefore needs a Menu key. For devices where Menu is a physical button this flag is
- * ignored, but on devices where the Menu key is drawn in software it may be hidden unless
- * this flag is set.
- *
- * (Note that Action Bars, when available, are the preferred way to offer additional
- * functions otherwise accessed via an options menu.)
- *
- * {@hide}
+ * Window flag: When requesting layout with an attached window, the attached window may
+ * overlap with the screen decorations of the parent window such as the navigation bar. By
+ * including this flag, the window manager will layout the attached window within the decor
+ * frame of the parent window such that it doesn't overlap with screen decorations.
*/
- public static final int FLAG_NEEDS_MENU_KEY = 0x40000000;
+ public static final int FLAG_LAYOUT_ATTACHED_IN_DECOR = 0x40000000;
/**
* Flag indicating that this Window is responsible for drawing the background for the
@@ -1065,16 +1062,6 @@ public interface WindowManager extends ViewManager {
*/
public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004;
- /**
- * This is set for a window that has explicitly specified its
- * FLAG_NEEDS_MENU_KEY, so we know the value on this window is the
- * appropriate one to use. If this is not set, we should look at
- * windows behind it to determine the appropriate value.
- *
- * @hide
- */
- public static final int PRIVATE_FLAG_SET_NEEDS_MENU_KEY = 0x00000008;
-
/** In a multiuser system if this flag is set and the owner is a system process then this
* window will appear on all user screens. This overrides the default behavior of window
* types that normally only appear on the owning user's screen. Refer to each window type
@@ -1116,12 +1103,58 @@ public interface WindowManager extends ViewManager {
public static final int PRIVATE_FLAG_KEYGUARD = 0x00000400;
/**
+ * Flag that prevents the wallpaper behind the current window from receiving touch events.
+ *
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800;
+
+ /**
* Control flags that are private to the platform.
* @hide
*/
public int privateFlags;
/**
+ * Value for {@link #needsMenuKey} for a window that has not explicitly specified if it
+ * needs {@link #NEEDS_MENU_SET_TRUE} or doesn't need {@link #NEEDS_MENU_SET_FALSE} a menu
+ * key. For this case, we should look at windows behind it to determine the appropriate
+ * value.
+ *
+ * @hide
+ */
+ public static final int NEEDS_MENU_UNSET = 0;
+
+ /**
+ * Value for {@link #needsMenuKey} for a window that has explicitly specified it needs a
+ * menu key.
+ *
+ * @hide
+ */
+ public static final int NEEDS_MENU_SET_TRUE = 1;
+
+ /**
+ * Value for {@link #needsMenuKey} for a window that has explicitly specified it doesn't
+ * needs a menu key.
+ *
+ * @hide
+ */
+ public static final int NEEDS_MENU_SET_FALSE = 2;
+
+ /**
+ * State variable for a window belonging to an activity that responds to
+ * {@link KeyEvent#KEYCODE_MENU} and therefore needs a Menu key. For devices where Menu is a
+ * physical button this variable is ignored, but on devices where the Menu key is drawn in
+ * software it may be hidden unless this variable is set to {@link #NEEDS_MENU_SET_TRUE}.
+ *
+ * (Note that Action Bars, when available, are the preferred way to offer additional
+ * functions otherwise accessed via an options menu.)
+ *
+ * {@hide}
+ */
+ public int needsMenuKey = NEEDS_MENU_UNSET;
+
+ /**
* Given a particular set of window manager flags, determine whether
* such a window may be a target for an input method when it has
* focus. In particular, this checks the
@@ -1129,9 +1162,9 @@ public interface WindowManager extends ViewManager {
* flags and returns true if the combination of the two corresponds
* to a window that needs to be behind the input method so that the
* user can type into it.
- *
+ *
* @param flags The current window manager flags.
- *
+ *
* @return Returns true if such a window should be behind/interact
* with an input method, false if not.
*/
@@ -1299,7 +1332,7 @@ public interface WindowManager extends ViewManager {
*
* @hide
*/
- public Rect surfaceInsets = new Rect();
+ public final Rect surfaceInsets = new Rect();
/**
* The desired bitmap format. May be one of the constants in
@@ -1558,7 +1591,19 @@ public interface WindowManager extends ViewManager {
public final CharSequence getTitle() {
return mTitle;
}
-
+
+ /** @hide */
+ @SystemApi
+ public final void setUserActivityTimeout(long timeout) {
+ userActivityTimeout = timeout;
+ }
+
+ /** @hide */
+ @SystemApi
+ public final long getUserActivityTimeout() {
+ return userActivityTimeout;
+ }
+
public int describeContents() {
return 0;
}
@@ -1596,14 +1641,15 @@ public interface WindowManager extends ViewManager {
out.writeInt(surfaceInsets.top);
out.writeInt(surfaceInsets.right);
out.writeInt(surfaceInsets.bottom);
+ out.writeInt(needsMenuKey);
}
-
+
public static final Parcelable.Creator<LayoutParams> CREATOR
= new Parcelable.Creator<LayoutParams>() {
public LayoutParams createFromParcel(Parcel in) {
return new LayoutParams(in);
}
-
+
public LayoutParams[] newArray(int size) {
return new LayoutParams[size];
}
@@ -1643,8 +1689,9 @@ public interface WindowManager extends ViewManager {
surfaceInsets.top = in.readInt();
surfaceInsets.right = in.readInt();
surfaceInsets.bottom = in.readInt();
+ needsMenuKey = in.readInt();
}
-
+
@SuppressWarnings({"PointlessBitwiseExpression"})
public static final int LAYOUT_CHANGED = 1<<0;
public static final int TYPE_CHANGED = 1<<1;
@@ -1678,14 +1725,16 @@ public interface WindowManager extends ViewManager {
/** {@hide} */
public static final int PREFERRED_REFRESH_RATE_CHANGED = 1 << 21;
/** {@hide} */
+ public static final int NEEDS_MENU_KEY_CHANGED = 1 << 22;
+ /** {@hide} */
public static final int EVERYTHING_CHANGED = 0xffffffff;
// internal buffer to backup/restore parameters under compatibility mode.
private int[] mCompatibilityParamsBackup = null;
-
+
public final int copyFrom(LayoutParams o) {
int changes = 0;
-
+
if (width != o.width) {
width = o.width;
changes |= LAYOUT_CHANGED;
@@ -1822,9 +1871,14 @@ public interface WindowManager extends ViewManager {
changes |= SURFACE_INSETS_CHANGED;
}
+ if (needsMenuKey != o.needsMenuKey) {
+ needsMenuKey = o.needsMenuKey;
+ changes |= NEEDS_MENU_KEY_CHANGED;
+ }
+
return changes;
}
-
+
@Override
public String debug(String output) {
output += "Contents of " + this + ":";
@@ -1928,6 +1982,10 @@ public interface WindowManager extends ViewManager {
if (!surfaceInsets.equals(Insets.NONE)) {
sb.append(" surfaceInsets=").append(surfaceInsets);
}
+ if (needsMenuKey != NEEDS_MENU_UNSET) {
+ sb.append(" needsMenuKey=");
+ sb.append(needsMenuKey);
+ }
sb.append('}');
return sb.toString();
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 5926d5f..a14c766 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -103,6 +103,7 @@ public final class WindowManagerGlobal {
public static final int ADD_MULTIPLE_SINGLETON = -7;
public static final int ADD_PERMISSION_DENIED = -8;
public static final int ADD_INVALID_DISPLAY = -9;
+ public static final int ADD_INVALID_TYPE = -10;
private static WindowManagerGlobal sDefaultWindowManager;
private static IWindowManager sWindowManagerService;
@@ -121,6 +122,10 @@ public final class WindowManagerGlobal {
private WindowManagerGlobal() {
}
+ public static void initialize() {
+ getWindowManagerService();
+ }
+
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
@@ -135,6 +140,12 @@ public final class WindowManagerGlobal {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
+ try {
+ sWindowManagerService = getWindowManagerService();
+ ValueAnimator.setDurationScale(sWindowManagerService.getCurrentAnimatorScale());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get WindowManagerService, cannot set animator scale", e);
+ }
}
return sWindowManagerService;
}
@@ -154,7 +165,6 @@ public final class WindowManagerGlobal {
}
},
imm.getClient(), imm.getInputContext());
- ValueAnimator.setDurationScale(windowManager.getCurrentAnimatorScale());
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 52d79f8..98e9f54 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -16,6 +16,9 @@
package android.view;
+import android.annotation.NonNull;
+import android.os.IBinder;
+
/**
* Provides low-level communication with the system window manager for
* operations that are bound to a particular context, display or parent window.
@@ -47,6 +50,8 @@ public final class WindowManagerImpl implements WindowManager {
private final Display mDisplay;
private final Window mParentWindow;
+ private IBinder mDefaultToken;
+
public WindowManagerImpl(Display display) {
this(display, null);
}
@@ -64,16 +69,43 @@ public final class WindowManagerImpl implements WindowManager {
return new WindowManagerImpl(display, mParentWindow);
}
+ /**
+ * Sets the window token to assign when none is specified by the client or
+ * available from the parent window.
+ *
+ * @param token The default token to assign.
+ */
+ public void setDefaultToken(IBinder token) {
+ mDefaultToken = token;
+ }
+
@Override
- public void addView(View view, ViewGroup.LayoutParams params) {
+ public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
+ applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
@Override
- public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
+ public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
+ applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
+ private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
+ // Only use the default token if we don't have a parent window.
+ if (mDefaultToken != null && mParentWindow == null) {
+ if (!(params instanceof WindowManager.LayoutParams)) {
+ throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
+ }
+
+ // Only use the default token if we don't already have a token.
+ final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
+ if (wparams.token == null) {
+ wparams.token = mDefaultToken;
+ }
+ }
+ }
+
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index 38e3723..f557b97 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -173,4 +173,20 @@ public abstract class WindowManagerInternal {
* redrawn.
*/
public abstract void waitForAllWindowsDrawn(Runnable callback, long timeout);
+
+ /**
+ * Adds a window token for a given window type.
+ *
+ * @param token The token to add.
+ * @param type The window type.
+ */
+ public abstract void addWindowToken(android.os.IBinder token, int type);
+
+ /**
+ * Removes a window token.
+ *
+ * @param token The toke to remove.
+ * @param removeWindows Whether to also remove the windows associated with the token.
+ */
+ public abstract void removeWindowToken(android.os.IBinder token, boolean removeWindows);
}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 673f075..780ca99 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -17,6 +17,7 @@
package android.view;
import android.annotation.IntDef;
+import android.annotation.SystemApi;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
@@ -105,6 +106,13 @@ public interface WindowManagerPolicy {
public final static String EXTRA_HDMI_PLUGGED_STATE = "state";
/**
+ * Set to {@code true} when intent was invoked from pressing the home key.
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_FROM_HOME_KEY = "android.intent.extra.FROM_HOME_KEY";
+
+ /**
* Pass this event to the user / app. To be returned from
* {@link #interceptKeyBeforeQueueing}.
*/
@@ -865,12 +873,15 @@ public interface WindowManagerPolicy {
* Return the insets for the areas covered by system windows. These values
* are computed on the most recent layout, so they are not guaranteed to
* be correct.
- *
+ *
* @param attrs The LayoutParams of the window.
- * @param contentInset The areas covered by system windows, expressed as positive insets
- *
+ * @param outContentInsets The areas covered by system windows, expressed as positive insets.
+ * @param outStableInsets The areas covered by stable system windows irrespective of their
+ * current visibility. Expressed as positive insets.
+ *
*/
- public void getContentInsetHintLw(WindowManager.LayoutParams attrs, Rect contentInset);
+ public void getInsetHintLw(WindowManager.LayoutParams attrs, Rect outContentInsets,
+ Rect outStableInsets);
/**
* Called when layout of the windows is finished. After this function has
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index a218e4d..d0dde00 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -40,6 +40,9 @@ final class AccessibilityCache {
private final Object mLock = new Object();
+ private long mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+ private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+
private final SparseArray<AccessibilityWindowInfo> mWindowCache =
new SparseArray<>();
@@ -73,11 +76,32 @@ final class AccessibilityCache {
synchronized (mLock) {
final int eventType = event.getEventType();
switch (eventType) {
- case AccessibilityEvent.TYPE_VIEW_FOCUSED:
- case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
- case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
+ case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
+ if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
+ refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
+ }
+ mAccessibilityFocus = event.getSourceNodeId();
+ refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
+ } break;
+
+ case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
+ if (mAccessibilityFocus == event.getSourceNodeId()) {
+ refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
+ mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+ }
+ } break;
+
+ case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
+ if (mInputFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
+ refreshCachedNodeLocked(event.getWindowId(), mInputFocus);
+ }
+ mInputFocus = event.getSourceNodeId();
+ refreshCachedNodeLocked(event.getWindowId(), mInputFocus);
+ } break;
+
case AccessibilityEvent.TYPE_VIEW_SELECTED:
case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
+ case AccessibilityEvent.TYPE_VIEW_CLICKED:
case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
refreshCachedNodeLocked(event.getWindowId(), event.getSourceNodeId());
} break;
@@ -267,6 +291,9 @@ final class AccessibilityCache {
final int windowId = mNodeCache.keyAt(i);
clearNodesForWindowLocked(windowId);
}
+
+ mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+ mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 374f7e0..cefd34d 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -99,8 +99,6 @@ public final class AccessibilityInteractionClient
private boolean mPerformAccessibilityActionResult;
- private Point mComputeClickPointResult;
-
private Message mSameThreadMessage;
private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
@@ -522,43 +520,6 @@ public final class AccessibilityInteractionClient
return false;
}
- /**
- * Computes a point in screen coordinates where sending a down/up events would
- * perform a click on an {@link AccessibilityNodeInfo}.
- *
- * @param connectionId The id of a connection for interacting with the system.
- * @param accessibilityWindowId A unique window id. Use
- * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
- * to query the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use
- * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
- * to start from the root.
- * @return Point the click point of null if no such point.
- */
- public Point computeClickPointInScreen(int connectionId, int accessibilityWindowId,
- long accessibilityNodeId) {
- try {
- IAccessibilityServiceConnection connection = getConnection(connectionId);
- if (connection != null) {
- final int interactionId = mInteractionIdCounter.getAndIncrement();
- final boolean success = connection.computeClickPointInScreen(
- accessibilityWindowId, accessibilityNodeId,
- interactionId, this, Thread.currentThread().getId());
- if (success) {
- return getComputeClickPointInScreenResultAndClear(interactionId);
- }
- } else {
- if (DEBUG) {
- Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
- }
- }
- } catch (RemoteException re) {
- Log.w(LOG_TAG, "Error while calling remote computeClickPointInScreen", re);
- }
- return null;
- }
-
public void clearCache() {
sAccessibilityCache.clear();
}
@@ -674,34 +635,6 @@ public final class AccessibilityInteractionClient
}
/**
- * Gets the result of a request to compute a point in screen for clicking on a node.
- *
- * @param interactionId The interaction id to match the result with the request.
- * @return The point or null if no such point.
- */
- private Point getComputeClickPointInScreenResultAndClear(int interactionId) {
- synchronized (mInstanceLock) {
- final boolean success = waitForResultTimedLocked(interactionId);
- Point result = success ? mComputeClickPointResult : null;
- clearResultLocked();
- return result;
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public void setComputeClickPointInScreenActionResult(Point point, int interactionId) {
- synchronized (mInstanceLock) {
- if (interactionId > mInteractionId) {
- mComputeClickPointResult = point;
- mInteractionId = interactionId;
- }
- mInstanceLock.notifyAll();
- }
- }
-
- /**
* Clears the result state.
*/
private void clearResultLocked() {
@@ -709,7 +642,6 @@ public final class AccessibilityInteractionClient
mFindAccessibilityNodeInfoResult = null;
mFindAccessibilityNodeInfosResult = null;
mPerformAccessibilityActionResult = false;
- mComputeClickPointResult = null;
}
/**
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index c3b17db..e77b862 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -459,7 +459,7 @@ public final class AccessibilityManager {
*/
public boolean addAccessibilityStateChangeListener(
@NonNull AccessibilityStateChangeListener listener) {
- // Final CopyOnArrayList - no lock needed.
+ // Final CopyOnWriteArrayList - no lock needed.
return mAccessibilityStateChangeListeners.add(listener);
}
@@ -471,7 +471,7 @@ public final class AccessibilityManager {
*/
public boolean removeAccessibilityStateChangeListener(
@NonNull AccessibilityStateChangeListener listener) {
- // Final CopyOnArrayList - no lock needed.
+ // Final CopyOnWriteArrayList - no lock needed.
return mAccessibilityStateChangeListeners.remove(listener);
}
@@ -484,7 +484,7 @@ public final class AccessibilityManager {
*/
public boolean addTouchExplorationStateChangeListener(
@NonNull TouchExplorationStateChangeListener listener) {
- // Final CopyOnArrayList - no lock needed.
+ // Final CopyOnWriteArrayList - no lock needed.
return mTouchExplorationStateChangeListeners.add(listener);
}
@@ -496,7 +496,7 @@ public final class AccessibilityManager {
*/
public boolean removeTouchExplorationStateChangeListener(
@NonNull TouchExplorationStateChangeListener listener) {
- // Final CopyOnArrayList - no lock needed.
+ // Final CopyOnWriteArrayList - no lock needed.
return mTouchExplorationStateChangeListeners.remove(listener);
}
@@ -511,7 +511,7 @@ public final class AccessibilityManager {
*/
public boolean addHighTextContrastStateChangeListener(
@NonNull HighTextContrastChangeListener listener) {
- // Final CopyOnArrayList - no lock needed.
+ // Final CopyOnWriteArrayList - no lock needed.
return mHighTextContrastStateChangeListeners.add(listener);
}
@@ -525,7 +525,7 @@ public final class AccessibilityManager {
*/
public boolean removeHighTextContrastStateChangeListener(
@NonNull HighTextContrastChangeListener listener) {
- // Final CopyOnArrayList - no lock needed.
+ // Final CopyOnWriteArrayList - no lock needed.
return mHighTextContrastStateChangeListeners.remove(listener);
}
@@ -640,9 +640,9 @@ public final class AccessibilityManager {
synchronized (mLock) {
isEnabled = mIsEnabled;
}
- final int listenerCount = mAccessibilityStateChangeListeners.size();
- for (int i = 0; i < listenerCount; i++) {
- mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(isEnabled);
+ // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
+ for (AccessibilityStateChangeListener listener :mAccessibilityStateChangeListeners) {
+ listener.onAccessibilityStateChanged(isEnabled);
}
}
@@ -654,10 +654,9 @@ public final class AccessibilityManager {
synchronized (mLock) {
isTouchExplorationEnabled = mIsTouchExplorationEnabled;
}
- final int listenerCount = mTouchExplorationStateChangeListeners.size();
- for (int i = 0; i < listenerCount; i++) {
- mTouchExplorationStateChangeListeners.get(i)
- .onTouchExplorationStateChanged(isTouchExplorationEnabled);
+ // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
+ for (TouchExplorationStateChangeListener listener :mTouchExplorationStateChangeListeners) {
+ listener.onTouchExplorationStateChanged(isTouchExplorationEnabled);
}
}
@@ -669,10 +668,9 @@ public final class AccessibilityManager {
synchronized (mLock) {
isHighTextContrastEnabled = mIsHighTextContrastEnabled;
}
- final int listenerCount = mHighTextContrastStateChangeListeners.size();
- for (int i = 0; i < listenerCount; i++) {
- mHighTextContrastStateChangeListeners.get(i)
- .onHighTextContrastStateChanged(isHighTextContrastEnabled);
+ // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
+ for (HighTextContrastChangeListener listener : mHighTextContrastStateChangeListeners) {
+ listener.onHighTextContrastStateChanged(isHighTextContrastEnabled);
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 3987fbc..b5afdf7 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -547,6 +547,8 @@ public class AccessibilityNodeInfo implements Parcelable {
private long mParentNodeId = ROOT_NODE_ID;
private long mLabelForId = ROOT_NODE_ID;
private long mLabeledById = ROOT_NODE_ID;
+ private long mTraversalBefore = ROOT_NODE_ID;
+ private long mTraversalAfter = ROOT_NODE_ID;
private int mBooleanProperties;
private final Rect mBoundsInParent = new Rect();
@@ -1046,6 +1048,126 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Gets the node before which this one is visited during traversal. A screen-reader
+ * must visit the content of this node before the content of the one it precedes.
+ *
+ * @return The succeeding node if such or <code>null</code>.
+ *
+ * @see #setTraversalBefore(android.view.View)
+ * @see #setTraversalBefore(android.view.View, int)
+ */
+ public AccessibilityNodeInfo getTraversalBefore() {
+ enforceSealed();
+ return getNodeForAccessibilityId(mTraversalBefore);
+ }
+
+ /**
+ * Sets the view before whose node this one should be visited during traversal. A
+ * screen-reader must visit the content of this node before the content of the one
+ * it precedes.
+ * <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 view The view providing the preceding node.
+ *
+ * @see #getTraversalBefore()
+ */
+ public void setTraversalBefore(View view) {
+ setTraversalBefore(view, UNDEFINED_ITEM_ID);
+ }
+
+ /**
+ * Sets the node before which this one is visited during traversal. A screen-reader
+ * must visit the content of this node before the content of the one it precedes.
+ * The successor is a virtual descendant of the given <code>root</code>. If
+ * <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root is set
+ * as the successor.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report them selves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </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 root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setTraversalBefore(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final int rootAccessibilityViewId = (root != null)
+ ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ mTraversalBefore = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
+ * Gets the node after which this one is visited in accessibility traversal.
+ * A screen-reader must visit the content of the other node before the content
+ * of this one.
+ *
+ * @return The succeeding node if such or <code>null</code>.
+ *
+ * @see #setTraversalAfter(android.view.View)
+ * @see #setTraversalAfter(android.view.View, int)
+ */
+ public AccessibilityNodeInfo getTraversalAfter() {
+ enforceSealed();
+ return getNodeForAccessibilityId(mTraversalAfter);
+ }
+
+ /**
+ * Sets the view whose node is visited after this one in accessibility traversal.
+ * A screen-reader must visit the content of the other node before the content
+ * of this one.
+ * <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 view The previous view.
+ *
+ * @see #getTraversalAfter()
+ */
+ public void setTraversalAfter(View view) {
+ setTraversalAfter(view, UNDEFINED_ITEM_ID);
+ }
+
+ /**
+ * Sets the node after which this one is visited in accessibility traversal.
+ * A screen-reader must visit the content of the other node before the content
+ * of this one. If <code>virtualDescendantId</code> equals to {@link View#NO_ID}
+ * the root is set as the predecessor.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report them selves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </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 root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setTraversalAfter(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final int rootAccessibilityViewId = (root != null)
+ ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ mTraversalAfter = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
* Sets the maximum text length, or -1 for no limit.
* <p>
* Typically used to indicate that an editable text field has a limit on
@@ -1229,13 +1351,7 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public AccessibilityNodeInfo getParent() {
enforceSealed();
- if (!canPerformRequestOverConnection(mParentNodeId)) {
- return null;
- }
- AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
- return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
- mWindowId, mParentNodeId, false, FLAG_PREFETCH_PREDECESSORS
- | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
+ return getNodeForAccessibilityId(mParentNodeId);
}
/**
@@ -2055,13 +2171,7 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public AccessibilityNodeInfo getLabelFor() {
enforceSealed();
- if (!canPerformRequestOverConnection(mLabelForId)) {
- return null;
- }
- AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
- return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
- mWindowId, mLabelForId, false, FLAG_PREFETCH_PREDECESSORS
- | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
+ return getNodeForAccessibilityId(mLabelForId);
}
/**
@@ -2113,13 +2223,7 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public AccessibilityNodeInfo getLabeledBy() {
enforceSealed();
- if (!canPerformRequestOverConnection(mLabeledById)) {
- return null;
- }
- AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
- return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
- mWindowId, mLabeledById, false, FLAG_PREFETCH_PREDECESSORS
- | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
+ return getNodeForAccessibilityId(mLabeledById);
}
/**
@@ -2453,6 +2557,9 @@ public class AccessibilityNodeInfo implements Parcelable {
parcel.writeLong(mParentNodeId);
parcel.writeLong(mLabelForId);
parcel.writeLong(mLabeledById);
+ parcel.writeLong(mTraversalBefore);
+ parcel.writeLong(mTraversalAfter);
+
parcel.writeInt(mConnectionId);
final LongArray childIds = mChildNodeIds;
@@ -2571,6 +2678,8 @@ public class AccessibilityNodeInfo implements Parcelable {
mParentNodeId = other.mParentNodeId;
mLabelForId = other.mLabelForId;
mLabeledById = other.mLabeledById;
+ mTraversalBefore = other.mTraversalBefore;
+ mTraversalAfter = other.mTraversalAfter;
mWindowId = other.mWindowId;
mConnectionId = other.mConnectionId;
mBoundsInParent.set(other.mBoundsInParent);
@@ -2633,6 +2742,9 @@ public class AccessibilityNodeInfo implements Parcelable {
mParentNodeId = parcel.readLong();
mLabelForId = parcel.readLong();
mLabeledById = parcel.readLong();
+ mTraversalBefore = parcel.readLong();
+ mTraversalAfter = parcel.readLong();
+
mConnectionId = parcel.readInt();
final int childrenSize = parcel.readInt();
@@ -2725,6 +2837,8 @@ public class AccessibilityNodeInfo implements Parcelable {
mParentNodeId = ROOT_NODE_ID;
mLabelForId = ROOT_NODE_ID;
mLabeledById = ROOT_NODE_ID;
+ mTraversalBefore = ROOT_NODE_ID;
+ mTraversalAfter = ROOT_NODE_ID;
mWindowId = UNDEFINED_ITEM_ID;
mConnectionId = UNDEFINED_CONNECTION_ID;
mMaxTextLength = -1;
@@ -2911,6 +3025,8 @@ public class AccessibilityNodeInfo implements Parcelable {
builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId));
builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId));
builder.append("; mParentNodeId: " + mParentNodeId);
+ builder.append("; traversalBefore: ").append(mTraversalBefore);
+ builder.append("; traversalAfter: ").append(mTraversalAfter);
int granularities = mMovementGranularities;
builder.append("; MovementGranularities: [");
@@ -2963,6 +3079,16 @@ public class AccessibilityNodeInfo implements Parcelable {
return builder.toString();
}
+ private AccessibilityNodeInfo getNodeForAccessibilityId(long accessibilityId) {
+ if (!canPerformRequestOverConnection(accessibilityId)) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
+ mWindowId, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
+ | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
+ }
+
/**
* 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.
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index ad55f5f..e1942be 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -51,11 +51,24 @@ public final class AccessibilityWindowInfo implements Parcelable {
*/
public static final int TYPE_SYSTEM = 3;
+ /**
+ * Window type: Windows that are overlaid <em>only</em> by an {@link
+ * android.accessibilityservice.AccessibilityService} for interception of
+ * user interactions without changing the windows an accessibility service
+ * can introspect. In particular, an accessibility service can introspect
+ * only windows that a sighted user can interact with which they can touch
+ * these windows or can type into these windows. For example, if there
+ * is a full screen accessibility overlay that is touchable, the windows
+ * below it will be introspectable by an accessibility service regardless
+ * they are covered by a touchable window.
+ */
+ public static final int TYPE_ACCESSIBILITY_OVERLAY = 4;
+
private static final int UNDEFINED = -1;
private static final int BOOLEAN_PROPERTY_ACTIVE = 1 << 0;
private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 1;
- private static final int BOOLEAN_PROPERTY_ACCESSIBLITY_FOCUSED = 1 << 2;
+ private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 1 << 2;
// Housekeeping.
private static final int MAX_POOL_SIZE = 10;
@@ -85,6 +98,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
* @see #TYPE_APPLICATION
* @see #TYPE_INPUT_METHOD
* @see #TYPE_SYSTEM
+ * @see #TYPE_ACCESSIBILITY_OVERLAY
*/
public int getType() {
return mType;
@@ -93,7 +107,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
/**
* Sets the type of the window.
*
- * @param The type
+ * @param type The type
*
* @hide
*/
@@ -115,7 +129,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
* Sets the layer which determines the Z-order of the window. Windows
* with greater layer appear on top of windows with lesser layer.
*
- * @param The window layer.
+ * @param layer The window layer.
*
* @hide
*/
@@ -174,7 +188,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
/**
* Sets the unique window id.
*
- * @param windowId The window id.
+ * @param id The window id.
*
* @hide
*/
@@ -230,7 +244,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
* the user is currently touching or the window has input focus
* and the user is not touching any window.
*
- * @param Whether this is the active window.
+ * @param active Whether this is the active window.
*
* @hide
*/
@@ -250,7 +264,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
/**
* Sets if this window has input focus.
*
- * @param Whether has input focus.
+ * @param focused Whether has input focus.
*
* @hide
*/
@@ -264,18 +278,18 @@ public final class AccessibilityWindowInfo implements Parcelable {
* @return Whether has accessibility focus.
*/
public boolean isAccessibilityFocused() {
- return getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBLITY_FOCUSED);
+ return getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED);
}
/**
* Sets if this window has accessibility focus.
*
- * @param Whether has accessibility focus.
+ * @param focused Whether has accessibility focus.
*
* @hide
*/
public void setAccessibilityFocused(boolean focused) {
- setBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBLITY_FOCUSED, focused);
+ setBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED, focused);
}
/**
@@ -534,6 +548,9 @@ public final class AccessibilityWindowInfo implements Parcelable {
case TYPE_SYSTEM: {
return "TYPE_SYSTEM";
}
+ case TYPE_ACCESSIBILITY_OVERLAY: {
+ return "TYPE_ACCESSIBILITY_OVERLAY";
+ }
default:
return "<UNKNOWN>";
}
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index 66a3f46..cecc4af 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -54,8 +54,4 @@ oneway interface IAccessibilityInteractionConnection {
void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments,
int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid);
-
- void computeClickPointInScreen(long accessibilityNodeId, in Region bounds, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
- long interrogatingTid, in MagnificationSpec spec);
}
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
index f480216..42ae1b3 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -52,12 +52,4 @@ oneway interface IAccessibilityInteractionConnectionCallback {
* @param interactionId The interaction id to match the result with the request.
*/
void setPerformAccessibilityActionResult(boolean succeeded, int interactionId);
-
- /**
- * Sets the result of a request to compute a point for clicking in a view.
- *
- * @param point The point of null if no such point.
- * @param interactionId The interaction id to match the result with the request.
- */
- void setComputeClickPointInScreenActionResult(in Point point, int interactionId);
}
diff --git a/core/java/android/view/animation/AccelerateDecelerateInterpolator.java b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
index ed6949a..21d5a5b 100644
--- a/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
+++ b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
@@ -26,17 +26,17 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* An interpolator where the rate of change starts and ends slowly but
* accelerates through the middle.
- *
*/
@HasNativeInterpolator
-public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class AccelerateDecelerateInterpolator extends BaseInterpolator
+ implements NativeInterpolatorFactory {
public AccelerateDecelerateInterpolator() {
}
-
+
@SuppressWarnings({"UnusedDeclaration"})
public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
}
-
+
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
diff --git a/core/java/android/view/animation/AccelerateInterpolator.java b/core/java/android/view/animation/AccelerateInterpolator.java
index 1c75f16..6c8d7b1 100644
--- a/core/java/android/view/animation/AccelerateInterpolator.java
+++ b/core/java/android/view/animation/AccelerateInterpolator.java
@@ -33,7 +33,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
*
*/
@HasNativeInterpolator
-public class AccelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private final float mFactor;
private final double mDoubleFactor;
@@ -70,7 +70,7 @@ public class AccelerateInterpolator implements Interpolator, NativeInterpolatorF
mFactor = a.getFloat(R.styleable.AccelerateInterpolator_factor, 1.0f);
mDoubleFactor = 2 * mFactor;
-
+ setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index af4e04f..606c83e 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -321,7 +321,7 @@ public class AnimationUtils {
private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser)
throws XmlPullParserException, IOException {
- Interpolator interpolator = null;
+ BaseInterpolator interpolator = null;
// Make sure we are on a start tag.
int type;
@@ -361,10 +361,7 @@ public class AnimationUtils {
} else {
throw new RuntimeException("Unknown interpolator name: " + parser.getName());
}
-
}
-
return interpolator;
-
}
}
diff --git a/core/java/android/view/animation/AnticipateInterpolator.java b/core/java/android/view/animation/AnticipateInterpolator.java
index fe756bd..fb66c31 100644
--- a/core/java/android/view/animation/AnticipateInterpolator.java
+++ b/core/java/android/view/animation/AnticipateInterpolator.java
@@ -31,7 +31,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
* An interpolator where the change starts backward then flings forward.
*/
@HasNativeInterpolator
-public class AnticipateInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class AnticipateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private final float mTension;
public AnticipateInterpolator() {
@@ -60,9 +60,8 @@ public class AnticipateInterpolator implements Interpolator, NativeInterpolatorF
a = res.obtainAttributes(attrs, R.styleable.AnticipateInterpolator);
}
- mTension =
- a.getFloat(R.styleable.AnticipateInterpolator_tension, 2.0f);
-
+ mTension = a.getFloat(R.styleable.AnticipateInterpolator_tension, 2.0f);
+ setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
diff --git a/core/java/android/view/animation/AnticipateOvershootInterpolator.java b/core/java/android/view/animation/AnticipateOvershootInterpolator.java
index 78e5acf..1af72da 100644
--- a/core/java/android/view/animation/AnticipateOvershootInterpolator.java
+++ b/core/java/android/view/animation/AnticipateOvershootInterpolator.java
@@ -35,7 +35,8 @@ import static com.android.internal.R.styleable.AnticipateOvershootInterpolator;
* the target value and finally goes back to the final value.
*/
@HasNativeInterpolator
-public class AnticipateOvershootInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class AnticipateOvershootInterpolator extends BaseInterpolator
+ implements NativeInterpolatorFactory {
private final float mTension;
public AnticipateOvershootInterpolator() {
@@ -78,7 +79,7 @@ public class AnticipateOvershootInterpolator implements Interpolator, NativeInte
mTension = a.getFloat(AnticipateOvershootInterpolator_tension, 2.0f) *
a.getFloat(AnticipateOvershootInterpolator_extraTension, 1.5f);
-
+ setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
diff --git a/core/java/android/view/animation/BaseInterpolator.java b/core/java/android/view/animation/BaseInterpolator.java
new file mode 100644
index 0000000..9c0014c
--- /dev/null
+++ b/core/java/android/view/animation/BaseInterpolator.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 android.view.animation;
+
+/**
+ * An abstract class which is extended by default interpolators.
+ */
+abstract public class BaseInterpolator implements Interpolator {
+ private int mChangingConfiguration;
+ /**
+ * @hide
+ */
+ public int getChangingConfiguration() {
+ return mChangingConfiguration;
+ }
+
+ /**
+ * @hide
+ */
+ void setChangingConfiguration(int changingConfiguration) {
+ mChangingConfiguration = changingConfiguration;
+ }
+}
diff --git a/core/java/android/view/animation/BounceInterpolator.java b/core/java/android/view/animation/BounceInterpolator.java
index 9d8ca90..909eaa4 100644
--- a/core/java/android/view/animation/BounceInterpolator.java
+++ b/core/java/android/view/animation/BounceInterpolator.java
@@ -27,7 +27,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
* An interpolator where the change bounces at the end.
*/
@HasNativeInterpolator
-public class BounceInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class BounceInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public BounceInterpolator() {
}
diff --git a/core/java/android/view/animation/CycleInterpolator.java b/core/java/android/view/animation/CycleInterpolator.java
index 3114aa3..663c109 100644
--- a/core/java/android/view/animation/CycleInterpolator.java
+++ b/core/java/android/view/animation/CycleInterpolator.java
@@ -33,7 +33,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
*
*/
@HasNativeInterpolator
-public class CycleInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class CycleInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public CycleInterpolator(float cycles) {
mCycles = cycles;
}
@@ -52,7 +52,7 @@ public class CycleInterpolator implements Interpolator, NativeInterpolatorFactor
}
mCycles = a.getFloat(R.styleable.CycleInterpolator_cycles, 1.0f);
-
+ setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
diff --git a/core/java/android/view/animation/DecelerateInterpolator.java b/core/java/android/view/animation/DecelerateInterpolator.java
index 674207c..f426f60 100644
--- a/core/java/android/view/animation/DecelerateInterpolator.java
+++ b/core/java/android/view/animation/DecelerateInterpolator.java
@@ -33,7 +33,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
*
*/
@HasNativeInterpolator
-public class DecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class DecelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public DecelerateInterpolator() {
}
@@ -62,7 +62,7 @@ public class DecelerateInterpolator implements Interpolator, NativeInterpolatorF
}
mFactor = a.getFloat(R.styleable.DecelerateInterpolator_factor, 1.0f);
-
+ setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
diff --git a/core/java/android/view/animation/LinearInterpolator.java b/core/java/android/view/animation/LinearInterpolator.java
index 552c611..2a047b4 100644
--- a/core/java/android/view/animation/LinearInterpolator.java
+++ b/core/java/android/view/animation/LinearInterpolator.java
@@ -25,17 +25,16 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* An interpolator where the rate of change is constant
- *
*/
@HasNativeInterpolator
-public class LinearInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public LinearInterpolator() {
}
-
+
public LinearInterpolator(Context context, AttributeSet attrs) {
}
-
+
public float getInterpolation(float input) {
return input;
}
diff --git a/core/java/android/view/animation/OvershootInterpolator.java b/core/java/android/view/animation/OvershootInterpolator.java
index d6c2808..306688a 100644
--- a/core/java/android/view/animation/OvershootInterpolator.java
+++ b/core/java/android/view/animation/OvershootInterpolator.java
@@ -32,7 +32,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
* then comes back.
*/
@HasNativeInterpolator
-public class OvershootInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class OvershootInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private final float mTension;
public OvershootInterpolator() {
@@ -61,9 +61,8 @@ public class OvershootInterpolator implements Interpolator, NativeInterpolatorFa
a = res.obtainAttributes(attrs, R.styleable.OvershootInterpolator);
}
- mTension =
- a.getFloat(R.styleable.OvershootInterpolator_tension, 2.0f);
-
+ mTension = a.getFloat(R.styleable.OvershootInterpolator_tension, 2.0f);
+ setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
diff --git a/core/java/android/view/animation/PathInterpolator.java b/core/java/android/view/animation/PathInterpolator.java
index 945ecf0..eec5555 100644
--- a/core/java/android/view/animation/PathInterpolator.java
+++ b/core/java/android/view/animation/PathInterpolator.java
@@ -42,7 +42,7 @@ import com.android.internal.R;
* path.lineTo(1f, 1f);
* </pre></blockquote></p>
*/
-public class PathInterpolator implements Interpolator {
+public class PathInterpolator extends BaseInterpolator {
// This governs how accurate the approximation of the Path is.
private static final float PRECISION = 0.002f;
@@ -98,7 +98,7 @@ public class PathInterpolator implements Interpolator {
a = res.obtainAttributes(attrs, R.styleable.PathInterpolator);
}
parseInterpolatorFromTypeArray(a);
-
+ setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 6793634..bab1f3b 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1634,11 +1634,12 @@ public class WebView extends AbsoluteLayout
* the portion of the HTML document that needs to be drawn. These
* optimizations are transparent to the developers. However, under certain
* circumstances, an App developer may want to disable them:
- * 1. When an app uses {@link #onDraw} to do own drawing and accesses portions
- * of the page that is way outside the visible portion of the page.
- * 2. When an app uses {@link #capturePicture} to capture a very large HTML document.
- * Note that capturePicture is a deprecated API.
- *
+ * <ol>
+ * <li>When an app uses {@link #onDraw} to do own drawing and accesses portions
+ * of the page that is way outside the visible portion of the page.</li>
+ * <li>When an app uses {@link #capturePicture} to capture a very large HTML document.
+ * Note that capturePicture is a deprecated API.</li>
+ * </ol>
* Enabling drawing the entire HTML document has a significant performance
* cost. This method should be called before any WebViews are created.
*/
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index a247c46..ac360fa 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -106,7 +106,7 @@ public final class WebViewDelegate {
throw new IllegalArgumentException(canvas.getClass().getName()
+ " is not hardware accelerated");
}
- ((HardwareCanvas) canvas).callDrawGLFunction(nativeDrawGLFunctor);
+ ((HardwareCanvas) canvas).callDrawGLFunction2(nativeDrawGLFunctor);
}
/**
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 7b23d8f..474ef42 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -18,8 +18,8 @@ package android.webkit;
import android.annotation.SystemApi;
import android.app.ActivityManagerInternal;
-import android.app.Application;
import android.app.AppGlobals;
+import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -34,14 +34,14 @@ import android.os.Trace;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.Log;
+
import com.android.server.LocalServices;
+
import dalvik.system.VMRuntime;
import java.io.File;
import java.util.Arrays;
-import com.android.internal.os.Zygote;
-
/**
* Top level factory, used creating all the main WebView implementation classes.
*
@@ -91,6 +91,12 @@ public final class WebViewFactory {
// us honest and minimize usage of WebView internals when binding the proxy.
if (sProviderInstance != null) return sProviderInstance;
+ final int uid = android.os.Process.myUid();
+ if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {
+ throw new UnsupportedOperationException(
+ "For security reasons, WebView is not allowed in privileged processes");
+ }
+
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
try {
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6a34c80..1e269a3 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
@@ -611,6 +612,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private final int[] mScrollOffset = new int[2];
private final int[] mScrollConsumed = new int[2];
+ private final float[] mTmpPoint = new float[2];
+
// Used for offsetting MotionEvents that we feed to the VelocityTracker.
// In the future it would be nice to be able to give this to the VelocityTracker
// directly, or alternatively put a VT into absolute-positioning mode that only
@@ -2511,38 +2514,29 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* Positions the selector in a way that mimics touch.
*/
void positionSelectorLikeTouch(int position, View sel, float x, float y) {
- positionSelectorLikeFocus(position, sel);
-
- if (mSelector != null && position != INVALID_POSITION) {
- mSelector.setHotspot(x, y);
- }
+ positionSelector(position, sel, true, x, y);
}
/**
* 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);
-
- if (manageState) {
+ if (mSelector != null && mSelectorPosition != position && position != INVALID_POSITION) {
final Rect bounds = mSelectorRect;
final float x = bounds.exactCenterX();
final float y = bounds.exactCenterY();
- selector.setVisible(getVisibility() == VISIBLE, false);
- selector.setHotspot(x, y);
+ positionSelector(position, sel, true, x, y);
+ } else {
+ positionSelector(position, sel);
}
}
void positionSelector(int position, View sel) {
+ positionSelector(position, sel, false, -1, -1);
+ }
+
+ private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) {
+ final boolean positionChanged = position != mSelectorPosition;
if (position != INVALID_POSITION) {
mSelectorPosition = position;
}
@@ -2562,7 +2556,22 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// Update the selector drawable.
final Drawable selector = mSelector;
if (selector != null) {
+ if (positionChanged) {
+ // Wipe out the current selector state so that we can start
+ // over in the new position with a fresh state.
+ selector.setVisible(false, false);
+ selector.setState(StateSet.NOTHING);
+ }
selector.setBounds(selectorRect);
+ if (positionChanged) {
+ if (getVisibility() == VISIBLE) {
+ selector.setVisible(true, false);
+ }
+ updateSelectorState();
+ }
+ if (manageHotspot) {
+ selector.setHotspot(x, y);
+ }
}
final boolean isChildViewEnabled = mIsChildViewEnabled;
@@ -2665,7 +2674,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* @return True if the selector should be shown
*/
boolean shouldShowSelector() {
- return (!isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
+ return (isFocused() && !isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
}
private void drawSelector(Canvas canvas) {
@@ -3200,6 +3209,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// get the selector in the right state, but we don't want to press each child.
}
+ @Override
+ public void dispatchDrawableHotspotChanged(float x, float y) {
+ // Don't dispatch hotspot changes to children. We'll manually handle
+ // calling drawableHotspotChanged on the correct child.
+ }
+
/**
* Maps a point to a position in the list.
*
@@ -3258,6 +3273,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mLayoutMode = LAYOUT_NORMAL;
if (!mDataChanged) {
+ final float[] point = mTmpPoint;
+ point[0] = x;
+ point[1] = y;
+ transformPointToViewLocal(point, child);
+ child.drawableHotspotChanged(point[0], point[1]);
child.setPressed(true);
setPressed(true);
layoutChildren();
@@ -3758,10 +3778,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
// Otherwise, check containment within list bounds. If we're
// outside bounds, cancel any active presses.
+ final View motionView = getChildAt(mMotionPosition - mFirstPosition);
final float x = ev.getX(pointerIndex);
if (!pointInView(x, y, mTouchSlop)) {
setPressed(false);
- final View motionView = getChildAt(mMotionPosition - mFirstPosition);
if (motionView != null) {
motionView.setPressed(false);
}
@@ -3769,6 +3789,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mPendingCheckForTap : mPendingCheckForLongPress);
mTouchMode = TOUCH_MODE_DONE_WAITING;
updateSelectorState();
+ } else if (motionView != null) {
+ // Still within bounds, update the hotspot.
+ final float[] point = mTmpPoint;
+ point[0] = x;
+ point[1] = y;
+ transformPointToViewLocal(point, motionView);
+ motionView.drawableHotspotChanged(point[0], point[1]);
}
break;
case TOUCH_MODE_SCROLL:
@@ -4655,7 +4682,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (mPositionScroller == null) {
mPositionScroller = createPositionScroller();
}
- mPositionScroller.startWithOffset(position, offset, offset);
+ mPositionScroller.startWithOffset(position, offset);
}
/**
@@ -4912,9 +4939,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (position >= headerViewsCount && position < footerViewsStart) {
// The view will be rebound to new data, clear any
// system-managed transient state.
- if (child.isAccessibilityFocused()) {
- child.clearAccessibilityFocus();
- }
+ child.clearAccessibilityFocus();
mRecycler.addScrapView(child, position);
}
}
@@ -4935,9 +4960,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (position >= headerViewsCount && position < footerViewsStart) {
// The view will be rebound to new data, clear any
// system-managed transient state.
- if (child.isAccessibilityFocused()) {
- child.clearAccessibilityFocus();
- }
+ child.clearAccessibilityFocus();
mRecycler.addScrapView(child, position);
}
}
@@ -6422,6 +6445,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
// However, we will NOT place them into scrap views.
activeViews[i] = child;
+ // Remember the position so that setupChild() doesn't reset state.
+ lp.scrappedFromPosition = firstActivePosition + i;
}
}
}
@@ -6778,9 +6803,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
private void clearAccessibilityFromScrap(View view) {
- if (view.isAccessibilityFocused()) {
- view.clearAccessibilityFocus();
- }
+ view.clearAccessibilityFocus();
view.setAccessibilityDelegate(null);
}
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 6033c42..a3ce808 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -16,7 +16,6 @@
package android.widget;
-import android.animation.ObjectAnimator;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -65,9 +64,6 @@ public abstract class AbsSeekBar extends ProgressBar {
* progress.
*/
private int mKeyProgressIncrement = 1;
- private ObjectAnimator mPositionAnimator;
- private static final int PROGRESS_ANIMATION_DURATION = 250;
-
private static final int NO_ALPHA = 0xFF;
private float mDisabledAlpha;
@@ -267,6 +263,12 @@ public abstract class AbsSeekBar extends ProgressBar {
if (mHasThumbTintMode) {
mThumb.setTintMode(mThumbTintMode);
}
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mThumb.isStateful()) {
+ mThumb.setState(getDrawableState());
+ }
}
}
@@ -382,14 +384,15 @@ public abstract class AbsSeekBar extends ProgressBar {
void onProgressRefresh(float scale, boolean fromUser) {
super.onProgressRefresh(scale, fromUser);
- if (!isAnimationRunning()) {
- setThumbPos(scale);
- }
- }
+ final Drawable thumb = mThumb;
+ if (thumb != null) {
+ setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE);
- @Override
- void onAnimatePosition(float scale, boolean fromUser) {
- setThumbPos(scale);
+ // 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
@@ -434,18 +437,6 @@ public abstract class AbsSeekBar extends ProgressBar {
return max > 0 ? getProgress() / (float) max : 0;
}
- private void setThumbPos(float scale) {
- 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.
- invalidate();
-
- }
- }
-
/**
* Updates the thumb drawable bounds.
*
@@ -712,50 +703,18 @@ public abstract class AbsSeekBar extends ProgressBar {
case KeyEvent.KEYCODE_DPAD_RIGHT:
increment = isLayoutRtl() ? -increment : increment;
int progress = getProgress() + increment;
- if (progress > -mKeyProgressIncrement &&
- progress < getMax() + mKeyProgressIncrement) {
- animateSetProgress(progress);
+ if (progress > 0 && progress < getMax()) {
+ setProgress(progress, true);
onKeyChange();
return true;
}
+ break;
}
}
return super.onKeyDown(keyCode, event);
}
- boolean isAnimationRunning() {
- return mPositionAnimator != null && mPositionAnimator.isRunning();
- }
-
- /**
- * @hide
- */
- @Override
- public void setProgress(int progress, boolean fromUser) {
- if (isAnimationRunning()) {
- mPositionAnimator.cancel();
- }
- super.setProgress(progress, fromUser);
- }
-
- void animateSetProgress(int progress) {
- float curProgress = isAnimationRunning() ? getAnimationPosition() : getProgress();
-
- if (progress < 0) {
- progress = 0;
- } else if (progress > getMax()) {
- progress = getMax();
- }
- setProgressValueOnly(progress);
-
- mPositionAnimator = ObjectAnimator.ofFloat(this, "animationPosition", curProgress,
- progress);
- mPositionAnimator.setDuration(PROGRESS_ANIMATION_DURATION);
- mPositionAnimator.setAutoCancel(true);
- mPositionAnimator.start();
- }
-
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index d779124..18f15a0 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -655,15 +655,19 @@ public class ActionMenuPresenter extends BaseMenuPresenter
protected boolean setFrame(int l, int t, int r, int b) {
final boolean changed = super.setFrame(l, t, r, b);
- // Set up the hotspot bounds to be centered on the image.
+ // Set up the hotspot bounds to square and centered on the image.
final Drawable d = getDrawable();
final Drawable bg = getBackground();
if (d != null && bg != null) {
- final float[] pts = mTempPts;
- pts[0] = d.getBounds().centerX();
- getImageMatrix().mapPoints(pts);
- final int offset = (int) pts[0] - getWidth() / 2;
- bg.setHotspotBounds(offset, 0, getWidth() + offset, getHeight());
+ final int width = getWidth();
+ final int height = getHeight();
+ final int halfEdge = Math.max(width, height) / 2;
+ final int offsetX = getPaddingLeft() - getPaddingRight();
+ final int offsetY = getPaddingTop() - getPaddingBottom();
+ final int centerX = (width + offsetX) / 2;
+ final int centerY = (height + offsetY) / 2;
+ bg.setHotspotBounds(centerX - halfEdge, centerY - halfEdge,
+ centerX + halfEdge, centerY + halfEdge);
}
return changed;
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index 7198e52..0a8a01f 100644
--- a/core/java/android/widget/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -429,7 +429,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
}
final int childCount = getChildCount();
- final int midVertical = (top + bottom) / 2;
+ final int midVertical = (bottom - top) / 2;
final int dividerWidth = getDividerWidth();
int overflowWidth = 0;
int nonOverflowWidth = 0;
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index b9f891c..5e2394c 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -215,7 +215,12 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
private boolean mDesiredFocusableState;
private boolean mDesiredFocusableInTouchModeState;
+ /** Lazily-constructed runnable for dispatching selection events. */
private SelectionNotifier mSelectionNotifier;
+
+ /** Selection notifier that's waiting for the next layout pass. */
+ private SelectionNotifier mPendingSelectionNotifier;
+
/**
* When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
* This is used to layout the children during a layout pass.
@@ -854,39 +859,51 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
private class SelectionNotifier implements Runnable {
public void run() {
- if (mDataChanged) {
- // Data has changed between when this SelectionNotifier
- // was posted and now. We need to wait until the AdapterView
- // has been synched to the new data.
+ mPendingSelectionNotifier = null;
+
+ if (mDataChanged && getViewRootImpl() != null
+ && getViewRootImpl().isLayoutRequested()) {
+ // Data has changed between when this SelectionNotifier was
+ // posted and now. Postpone the notification until the next
+ // layout is complete and we run checkSelectionChanged().
if (getAdapter() != null) {
- post(this);
+ mPendingSelectionNotifier = this;
}
} else {
- fireOnSelected();
- performAccessibilityActionsOnSelected();
+ dispatchOnItemSelected();
}
}
}
void selectionChanged() {
+ // We're about to post or run the selection notifier, so we don't need
+ // a pending notifier.
+ mPendingSelectionNotifier = null;
+
if (mOnItemSelectedListener != null
|| AccessibilityManager.getInstance(mContext).isEnabled()) {
if (mInLayout || mBlockLayoutRequests) {
// If we are in a layout traversal, defer notification
// by posting. This ensures that the view tree is
- // in a consistent state and is able to accomodate
+ // in a consistent state and is able to accommodate
// new layout or invalidate requests.
if (mSelectionNotifier == null) {
mSelectionNotifier = new SelectionNotifier();
+ } else {
+ removeCallbacks(mSelectionNotifier);
}
post(mSelectionNotifier);
} else {
- fireOnSelected();
- performAccessibilityActionsOnSelected();
+ dispatchOnItemSelected();
}
}
}
+ private void dispatchOnItemSelected() {
+ fireOnSelected();
+ performAccessibilityActionsOnSelected();
+ }
+
private void fireOnSelected() {
if (mOnItemSelectedListener == null) {
return;
@@ -1042,12 +1059,22 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
notifySubtreeAccessibilityStateChangedIfNeeded();
}
+ /**
+ * Called after layout to determine whether the selection position needs to
+ * be updated. Also used to fire any pending selection events.
+ */
void checkSelectionChanged() {
if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
selectionChanged();
mOldSelectedPosition = mSelectedPosition;
mOldSelectedRowId = mSelectedRowId;
}
+
+ // If we have a pending selection notification -- and we won't if we
+ // just fired one in selectionChanged() -- run it now.
+ if (mPendingSelectionNotifier != null) {
+ mPendingSelectionNotifier.run();
+ }
}
/**
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index 10e56c7..5c05b5a 100644
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -99,12 +99,12 @@ public class AppSecurityPermissions {
public Drawable loadGroupIcon(PackageManager pm) {
if (icon != 0) {
- return loadIcon(pm);
+ return loadUnbadgedIcon(pm);
} else {
ApplicationInfo appInfo;
try {
appInfo = pm.getApplicationInfo(packageName, 0);
- return appInfo.loadIcon(pm);
+ return appInfo.loadUnbadgedIcon(pm);
} catch (NameNotFoundException e) {
}
}
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 3b16aba..e6392b9 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -1005,6 +1005,12 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
+
+ if (mTemporaryDetach) {
+ // If we are temporarily in the detach state, then do nothing.
+ return;
+ }
+
// Perform validation if the view is losing focus.
if (!focused) {
performValidation();
diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java
index ea60abb..ed59ea6 100644
--- a/core/java/android/widget/CalendarView.java
+++ b/core/java/android/widget/CalendarView.java
@@ -17,42 +17,24 @@
package android.widget;
import android.annotation.Widget;
-import android.app.Service;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
-import android.database.DataSetObserver;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Paint.Align;
-import android.graphics.Paint.Style;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
import android.util.AttributeSet;
-import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.TypedValue;
-import android.view.GestureDetector;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.AbsListView.OnScrollListener;
import com.android.internal.R;
+import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
-import libcore.icu.LocaleData;
-
/**
* This class is a calendar widget for displaying and selecting dates. The range
* of dates supported by this calendar is configurable. A user can select a date
@@ -74,13 +56,12 @@ import libcore.icu.LocaleData;
*/
@Widget
public class CalendarView extends FrameLayout {
+ private static final String LOG_TAG = "CalendarView";
- /**
- * Tag for logging.
- */
- private static final String LOG_TAG = CalendarView.class.getSimpleName();
+ private static final int MODE_HOLO = 0;
+ private static final int MODE_MATERIAL = 1;
- private CalendarViewDelegate mDelegate;
+ private final CalendarViewDelegate mDelegate;
/**
* The callback used to indicate the user changes the date.
@@ -113,7 +94,23 @@ public class CalendarView extends FrameLayout {
public CalendarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mDelegate = new LegacyCalendarViewDelegate(this, context, attrs, defStyleAttr, defStyleRes);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.CalendarView, defStyleAttr, defStyleRes);
+ final int mode = a.getInt(R.styleable.CalendarView_calendarViewMode, MODE_HOLO);
+ a.recycle();
+
+ switch (mode) {
+ case MODE_HOLO:
+ mDelegate = new CalendarViewLegacyDelegate(
+ this, context, attrs, defStyleAttr, defStyleRes);
+ break;
+ case MODE_MATERIAL:
+ mDelegate = new CalendarViewMaterialDelegate(
+ this, context, attrs, defStyleAttr, defStyleRes);
+ break;
+ default:
+ throw new IllegalArgumentException("invalid calendarViewMode attribute");
+ }
}
/**
@@ -326,16 +323,6 @@ public class CalendarView extends FrameLayout {
return mDelegate.getDateTextAppearance();
}
- @Override
- public void setEnabled(boolean enabled) {
- mDelegate.setEnabled(enabled);
- }
-
- @Override
- public boolean isEnabled() {
- return mDelegate.isEnabled();
- }
-
/**
* Gets the minimal date supported by this {@link CalendarView} in milliseconds
* since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
@@ -516,14 +503,12 @@ public class CalendarView extends FrameLayout {
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- mDelegate.onInitializeAccessibilityEvent(event);
+ event.setClassName(CalendarView.class.getName());
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- mDelegate.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(CalendarView.class.getName());
}
/**
@@ -560,9 +545,6 @@ public class CalendarView extends FrameLayout {
void setDateTextAppearance(int resourceId);
int getDateTextAppearance();
- void setEnabled(boolean enabled);
- boolean isEnabled();
-
void setMinDate(long minDate);
long getMinDate();
@@ -582,21 +564,26 @@ public class CalendarView extends FrameLayout {
void setOnDateChangeListener(OnDateChangeListener listener);
void onConfigurationChanged(Configuration newConfig);
- void onInitializeAccessibilityEvent(AccessibilityEvent event);
- void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
}
/**
* An abstract class which can be used as a start for CalendarView implementations
*/
abstract static class AbstractCalendarViewDelegate implements CalendarViewDelegate {
- // The delegator
- protected CalendarView mDelegator;
+ /** String for parsing dates. */
+ private static final String DATE_FORMAT = "MM/dd/yyyy";
- // The context
- protected Context mContext;
+ /** The default minimal date. */
+ protected static final String DEFAULT_MIN_DATE = "01/01/1900";
+
+ /** The default maximal date. */
+ protected static final String DEFAULT_MAX_DATE = "01/01/2100";
- // The current locale
+ /** Date format for parsing dates. */
+ protected static final DateFormat DATE_FORMATTER = new SimpleDateFormat(DATE_FORMAT);
+
+ protected CalendarView mDelegator;
+ protected Context mContext;
protected Locale mCurrentLocale;
AbstractCalendarViewDelegate(CalendarView delegator, Context context) {
@@ -613,821 +600,6 @@ public class CalendarView extends FrameLayout {
}
mCurrentLocale = locale;
}
- }
-
- /**
- * A delegate implementing the legacy CalendarView
- */
- private static class LegacyCalendarViewDelegate extends AbstractCalendarViewDelegate {
-
- /**
- * Default value whether to show week number.
- */
- private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true;
-
- /**
- * The number of milliseconds in a day.e
- */
- private static final long MILLIS_IN_DAY = 86400000L;
-
- /**
- * The number of day in a week.
- */
- private static final int DAYS_PER_WEEK = 7;
-
- /**
- * The number of milliseconds in a week.
- */
- private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY;
-
- /**
- * Affects when the month selection will change while scrolling upe
- */
- private static final int SCROLL_HYST_WEEKS = 2;
-
- /**
- * How long the GoTo fling animation should last.
- */
- private static final int GOTO_SCROLL_DURATION = 1000;
-
- /**
- * The duration of the adjustment upon a user scroll in milliseconds.
- */
- private static final int ADJUSTMENT_SCROLL_DURATION = 500;
-
- /**
- * How long to wait after receiving an onScrollStateChanged notification
- * before acting on it.
- */
- private static final int SCROLL_CHANGE_DELAY = 40;
-
- /**
- * String for parsing dates.
- */
- private static final String DATE_FORMAT = "MM/dd/yyyy";
-
- /**
- * The default minimal date.
- */
- private static final String DEFAULT_MIN_DATE = "01/01/1900";
-
- /**
- * The default maximal date.
- */
- private static final String DEFAULT_MAX_DATE = "01/01/2100";
-
- private static final int DEFAULT_SHOWN_WEEK_COUNT = 6;
-
- private static final int DEFAULT_DATE_TEXT_SIZE = 14;
-
- private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6;
-
- private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12;
-
- private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2;
-
- private static final int UNSCALED_BOTTOM_BUFFER = 20;
-
- private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1;
-
- private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1;
-
- private final int mWeekSeperatorLineWidth;
-
- private int mDateTextSize;
-
- private Drawable mSelectedDateVerticalBar;
-
- private final int mSelectedDateVerticalBarWidth;
-
- private int mSelectedWeekBackgroundColor;
-
- private int mFocusedMonthDateColor;
-
- private int mUnfocusedMonthDateColor;
-
- private int mWeekSeparatorLineColor;
-
- private int mWeekNumberColor;
-
- private int mWeekDayTextAppearanceResId;
-
- private int mDateTextAppearanceResId;
-
- /**
- * The top offset of the weeks list.
- */
- private int mListScrollTopOffset = 2;
-
- /**
- * The visible height of a week view.
- */
- private int mWeekMinVisibleHeight = 12;
-
- /**
- * The visible height of a week view.
- */
- private int mBottomBuffer = 20;
-
- /**
- * The number of shown weeks.
- */
- private int mShownWeekCount;
-
- /**
- * Flag whether to show the week number.
- */
- private boolean mShowWeekNumber;
-
- /**
- * The number of day per week to be shown.
- */
- private int mDaysPerWeek = 7;
-
- /**
- * The friction of the week list while flinging.
- */
- private float mFriction = .05f;
-
- /**
- * Scale for adjusting velocity of the week list while flinging.
- */
- private float mVelocityScale = 0.333f;
-
- /**
- * The adapter for the weeks list.
- */
- private WeeksAdapter mAdapter;
-
- /**
- * The weeks list.
- */
- private ListView mListView;
-
- /**
- * The name of the month to display.
- */
- private TextView mMonthName;
-
- /**
- * The header with week day names.
- */
- private ViewGroup mDayNamesHeader;
-
- /**
- * Cached labels for the week names header.
- */
- private String[] mDayLabels;
-
- /**
- * The first day of the week.
- */
- private int mFirstDayOfWeek;
-
- /**
- * Which month should be displayed/highlighted [0-11].
- */
- private int mCurrentMonthDisplayed = -1;
-
- /**
- * Used for tracking during a scroll.
- */
- private long mPreviousScrollPosition;
-
- /**
- * Used for tracking which direction the view is scrolling.
- */
- private boolean mIsScrollingUp = false;
-
- /**
- * The previous scroll state of the weeks ListView.
- */
- private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
-
- /**
- * The current scroll state of the weeks ListView.
- */
- private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
-
- /**
- * Listener for changes in the selected day.
- */
- private OnDateChangeListener mOnDateChangeListener;
-
- /**
- * Command for adjusting the position after a scroll/fling.
- */
- private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
-
- /**
- * Temporary instance to avoid multiple instantiations.
- */
- private Calendar mTempDate;
-
- /**
- * The first day of the focused month.
- */
- private Calendar mFirstDayOfMonth;
-
- /**
- * The start date of the range supported by this picker.
- */
- private Calendar mMinDate;
-
- /**
- * The end date of the range supported by this picker.
- */
- private Calendar mMaxDate;
-
- /**
- * Date format for parsing dates.
- */
- private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
-
- LegacyCalendarViewDelegate(CalendarView delegator, Context context, AttributeSet attrs,
- int defStyleAttr, int defStyleRes) {
- super(delegator, context);
-
- // initialization based on locale
- setCurrentLocale(Locale.getDefault());
-
- TypedArray attributesArray = context.obtainStyledAttributes(attrs,
- R.styleable.CalendarView, defStyleAttr, defStyleRes);
- mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber,
- DEFAULT_SHOW_WEEK_NUMBER);
- mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek,
- LocaleData.get(Locale.getDefault()).firstDayOfWeek);
- String minDate = attributesArray.getString(R.styleable.CalendarView_minDate);
- if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) {
- parseDate(DEFAULT_MIN_DATE, mMinDate);
- }
- String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate);
- if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) {
- parseDate(DEFAULT_MAX_DATE, mMaxDate);
- }
- if (mMaxDate.before(mMinDate)) {
- throw new IllegalArgumentException("Max date cannot be before min date.");
- }
- mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount,
- DEFAULT_SHOWN_WEEK_COUNT);
- mSelectedWeekBackgroundColor = attributesArray.getColor(
- R.styleable.CalendarView_selectedWeekBackgroundColor, 0);
- mFocusedMonthDateColor = attributesArray.getColor(
- R.styleable.CalendarView_focusedMonthDateColor, 0);
- mUnfocusedMonthDateColor = attributesArray.getColor(
- R.styleable.CalendarView_unfocusedMonthDateColor, 0);
- mWeekSeparatorLineColor = attributesArray.getColor(
- R.styleable.CalendarView_weekSeparatorLineColor, 0);
- mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0);
- mSelectedDateVerticalBar = attributesArray.getDrawable(
- R.styleable.CalendarView_selectedDateVerticalBar);
-
- mDateTextAppearanceResId = attributesArray.getResourceId(
- R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small);
- updateDateTextSize();
-
- mWeekDayTextAppearanceResId = attributesArray.getResourceId(
- R.styleable.CalendarView_weekDayTextAppearance,
- DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID);
- attributesArray.recycle();
-
- DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics();
- mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics);
- mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics);
- mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_BOTTOM_BUFFER, displayMetrics);
- mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics);
- mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics);
-
- LayoutInflater layoutInflater = (LayoutInflater) mContext
- .getSystemService(Service.LAYOUT_INFLATER_SERVICE);
- View content = layoutInflater.inflate(R.layout.calendar_view, null, false);
- mDelegator.addView(content);
-
- mListView = (ListView) mDelegator.findViewById(R.id.list);
- mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names);
- mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name);
-
- setUpHeader();
- setUpListView();
- setUpAdapter();
-
- // go to today or whichever is close to today min or max date
- mTempDate.setTimeInMillis(System.currentTimeMillis());
- if (mTempDate.before(mMinDate)) {
- goTo(mMinDate, false, true, true);
- } else if (mMaxDate.before(mTempDate)) {
- goTo(mMaxDate, false, true, true);
- } else {
- goTo(mTempDate, false, true, true);
- }
-
- mDelegator.invalidate();
- }
-
- @Override
- public void setShownWeekCount(int count) {
- if (mShownWeekCount != count) {
- mShownWeekCount = count;
- mDelegator.invalidate();
- }
- }
-
- @Override
- public int getShownWeekCount() {
- return mShownWeekCount;
- }
-
- @Override
- public void setSelectedWeekBackgroundColor(int color) {
- if (mSelectedWeekBackgroundColor != color) {
- mSelectedWeekBackgroundColor = color;
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- WeekView weekView = (WeekView) mListView.getChildAt(i);
- if (weekView.mHasSelectedDay) {
- weekView.invalidate();
- }
- }
- }
- }
-
- @Override
- public int getSelectedWeekBackgroundColor() {
- return mSelectedWeekBackgroundColor;
- }
-
- @Override
- public void setFocusedMonthDateColor(int color) {
- if (mFocusedMonthDateColor != color) {
- mFocusedMonthDateColor = color;
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- WeekView weekView = (WeekView) mListView.getChildAt(i);
- if (weekView.mHasFocusedDay) {
- weekView.invalidate();
- }
- }
- }
- }
-
- @Override
- public int getFocusedMonthDateColor() {
- return mFocusedMonthDateColor;
- }
-
- @Override
- public void setUnfocusedMonthDateColor(int color) {
- if (mUnfocusedMonthDateColor != color) {
- mUnfocusedMonthDateColor = color;
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- WeekView weekView = (WeekView) mListView.getChildAt(i);
- if (weekView.mHasUnfocusedDay) {
- weekView.invalidate();
- }
- }
- }
- }
-
- @Override
- public int getUnfocusedMonthDateColor() {
- return mFocusedMonthDateColor;
- }
-
- @Override
- public void setWeekNumberColor(int color) {
- if (mWeekNumberColor != color) {
- mWeekNumberColor = color;
- if (mShowWeekNumber) {
- invalidateAllWeekViews();
- }
- }
- }
-
- @Override
- public int getWeekNumberColor() {
- return mWeekNumberColor;
- }
-
- @Override
- public void setWeekSeparatorLineColor(int color) {
- if (mWeekSeparatorLineColor != color) {
- mWeekSeparatorLineColor = color;
- invalidateAllWeekViews();
- }
- }
-
- @Override
- public int getWeekSeparatorLineColor() {
- return mWeekSeparatorLineColor;
- }
-
- @Override
- public void setSelectedDateVerticalBar(int resourceId) {
- Drawable drawable = mDelegator.getContext().getDrawable(resourceId);
- setSelectedDateVerticalBar(drawable);
- }
-
- @Override
- public void setSelectedDateVerticalBar(Drawable drawable) {
- if (mSelectedDateVerticalBar != drawable) {
- mSelectedDateVerticalBar = drawable;
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- WeekView weekView = (WeekView) mListView.getChildAt(i);
- if (weekView.mHasSelectedDay) {
- weekView.invalidate();
- }
- }
- }
- }
-
- @Override
- public Drawable getSelectedDateVerticalBar() {
- return mSelectedDateVerticalBar;
- }
-
- @Override
- public void setWeekDayTextAppearance(int resourceId) {
- if (mWeekDayTextAppearanceResId != resourceId) {
- mWeekDayTextAppearanceResId = resourceId;
- setUpHeader();
- }
- }
-
- @Override
- public int getWeekDayTextAppearance() {
- return mWeekDayTextAppearanceResId;
- }
-
- @Override
- public void setDateTextAppearance(int resourceId) {
- if (mDateTextAppearanceResId != resourceId) {
- mDateTextAppearanceResId = resourceId;
- updateDateTextSize();
- invalidateAllWeekViews();
- }
- }
-
- @Override
- public int getDateTextAppearance() {
- return mDateTextAppearanceResId;
- }
-
- @Override
- public void setEnabled(boolean enabled) {
- mListView.setEnabled(enabled);
- }
-
- @Override
- public boolean isEnabled() {
- return mListView.isEnabled();
- }
-
- @Override
- public void setMinDate(long minDate) {
- mTempDate.setTimeInMillis(minDate);
- if (isSameDate(mTempDate, mMinDate)) {
- return;
- }
- mMinDate.setTimeInMillis(minDate);
- // make sure the current date is not earlier than
- // the new min date since the latter is used for
- // calculating the indices in the adapter thus
- // avoiding out of bounds error
- Calendar date = mAdapter.mSelectedDate;
- if (date.before(mMinDate)) {
- mAdapter.setSelectedDay(mMinDate);
- }
- // reinitialize the adapter since its range depends on min date
- mAdapter.init();
- if (date.before(mMinDate)) {
- setDate(mTempDate.getTimeInMillis());
- } else {
- // we go to the current date to force the ListView to query its
- // adapter for the shown views since we have changed the adapter
- // range and the base from which the later calculates item indices
- // note that calling setDate will not work since the date is the same
- goTo(date, false, true, false);
- }
- }
-
- @Override
- public long getMinDate() {
- return mMinDate.getTimeInMillis();
- }
-
- @Override
- public void setMaxDate(long maxDate) {
- mTempDate.setTimeInMillis(maxDate);
- if (isSameDate(mTempDate, mMaxDate)) {
- return;
- }
- mMaxDate.setTimeInMillis(maxDate);
- // reinitialize the adapter since its range depends on max date
- mAdapter.init();
- Calendar date = mAdapter.mSelectedDate;
- if (date.after(mMaxDate)) {
- setDate(mMaxDate.getTimeInMillis());
- } else {
- // we go to the current date to force the ListView to query its
- // adapter for the shown views since we have changed the adapter
- // range and the base from which the later calculates item indices
- // note that calling setDate will not work since the date is the same
- goTo(date, false, true, false);
- }
- }
-
- @Override
- public long getMaxDate() {
- return mMaxDate.getTimeInMillis();
- }
-
- @Override
- public void setShowWeekNumber(boolean showWeekNumber) {
- if (mShowWeekNumber == showWeekNumber) {
- return;
- }
- mShowWeekNumber = showWeekNumber;
- mAdapter.notifyDataSetChanged();
- setUpHeader();
- }
-
- @Override
- public boolean getShowWeekNumber() {
- return mShowWeekNumber;
- }
-
- @Override
- public void setFirstDayOfWeek(int firstDayOfWeek) {
- if (mFirstDayOfWeek == firstDayOfWeek) {
- return;
- }
- mFirstDayOfWeek = firstDayOfWeek;
- mAdapter.init();
- mAdapter.notifyDataSetChanged();
- setUpHeader();
- }
-
- @Override
- public int getFirstDayOfWeek() {
- return mFirstDayOfWeek;
- }
-
- @Override
- public void setDate(long date) {
- setDate(date, false, false);
- }
-
- @Override
- public void setDate(long date, boolean animate, boolean center) {
- mTempDate.setTimeInMillis(date);
- if (isSameDate(mTempDate, mAdapter.mSelectedDate)) {
- return;
- }
- goTo(mTempDate, animate, true, center);
- }
-
- @Override
- public long getDate() {
- return mAdapter.mSelectedDate.getTimeInMillis();
- }
-
- @Override
- public void setOnDateChangeListener(OnDateChangeListener listener) {
- mOnDateChangeListener = listener;
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- setCurrentLocale(newConfig.locale);
- }
-
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- event.setClassName(CalendarView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- info.setClassName(CalendarView.class.getName());
- }
-
- /**
- * Sets the current locale.
- *
- * @param locale The current locale.
- */
- @Override
- protected void setCurrentLocale(Locale locale) {
- super.setCurrentLocale(locale);
-
- mTempDate = getCalendarForLocale(mTempDate, locale);
- mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale);
- mMinDate = getCalendarForLocale(mMinDate, locale);
- mMaxDate = getCalendarForLocale(mMaxDate, locale);
- }
- private void updateDateTextSize() {
- TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes(
- mDateTextAppearanceResId, R.styleable.TextAppearance);
- mDateTextSize = dateTextAppearance.getDimensionPixelSize(
- R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE);
- dateTextAppearance.recycle();
- }
-
- /**
- * Invalidates all week views.
- */
- private void invalidateAllWeekViews() {
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View view = mListView.getChildAt(i);
- view.invalidate();
- }
- }
-
- /**
- * Gets a calendar for locale bootstrapped with the value of a given calendar.
- *
- * @param oldCalendar The old calendar.
- * @param locale The locale.
- */
- private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
- if (oldCalendar == null) {
- return Calendar.getInstance(locale);
- } else {
- final long currentTimeMillis = oldCalendar.getTimeInMillis();
- Calendar newCalendar = Calendar.getInstance(locale);
- newCalendar.setTimeInMillis(currentTimeMillis);
- return newCalendar;
- }
- }
-
- /**
- * @return True if the <code>firstDate</code> is the same as the <code>
- * secondDate</code>.
- */
- private static boolean isSameDate(Calendar firstDate, Calendar secondDate) {
- return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR)
- && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR));
- }
-
- /**
- * Creates a new adapter if necessary and sets up its parameters.
- */
- private void setUpAdapter() {
- if (mAdapter == null) {
- mAdapter = new WeeksAdapter(mContext);
- mAdapter.registerDataSetObserver(new DataSetObserver() {
- @Override
- public void onChanged() {
- if (mOnDateChangeListener != null) {
- Calendar selectedDay = mAdapter.getSelectedDay();
- mOnDateChangeListener.onSelectedDayChange(mDelegator,
- selectedDay.get(Calendar.YEAR),
- selectedDay.get(Calendar.MONTH),
- selectedDay.get(Calendar.DAY_OF_MONTH));
- }
- }
- });
- mListView.setAdapter(mAdapter);
- }
-
- // refresh the view with the new parameters
- mAdapter.notifyDataSetChanged();
- }
-
- /**
- * Sets up the strings to be used by the header.
- */
- private void setUpHeader() {
- mDayLabels = new String[mDaysPerWeek];
- for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) {
- int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i;
- mDayLabels[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
- DateUtils.LENGTH_SHORTEST);
- }
-
- TextView label = (TextView) mDayNamesHeader.getChildAt(0);
- if (mShowWeekNumber) {
- label.setVisibility(View.VISIBLE);
- } else {
- label.setVisibility(View.GONE);
- }
- for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) {
- label = (TextView) mDayNamesHeader.getChildAt(i);
- if (mWeekDayTextAppearanceResId > -1) {
- label.setTextAppearance(mContext, mWeekDayTextAppearanceResId);
- }
- if (i < mDaysPerWeek + 1) {
- label.setText(mDayLabels[i - 1]);
- label.setVisibility(View.VISIBLE);
- } else {
- label.setVisibility(View.GONE);
- }
- }
- mDayNamesHeader.invalidate();
- }
-
- /**
- * Sets all the required fields for the list view.
- */
- private void setUpListView() {
- // Configure the listview
- mListView.setDivider(null);
- mListView.setItemsCanFocus(true);
- mListView.setVerticalScrollBarEnabled(false);
- mListView.setOnScrollListener(new OnScrollListener() {
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- LegacyCalendarViewDelegate.this.onScrollStateChanged(view, scrollState);
- }
-
- public void onScroll(
- AbsListView view, int firstVisibleItem, int visibleItemCount,
- int totalItemCount) {
- LegacyCalendarViewDelegate.this.onScroll(view, firstVisibleItem,
- visibleItemCount, totalItemCount);
- }
- });
- // Make the scrolling behavior nicer
- mListView.setFriction(mFriction);
- mListView.setVelocityScale(mVelocityScale);
- }
-
- /**
- * This moves to the specified time in the view. If the time is not already
- * in range it will move the list so that the first of the month containing
- * the time is at the top of the view. If the new time is already in view
- * the list will not be scrolled unless forceScroll is true. This time may
- * optionally be highlighted as selected as well.
- *
- * @param date The time to move to.
- * @param animate Whether to scroll to the given time or just redraw at the
- * new location.
- * @param setSelected Whether to set the given time as selected.
- * @param forceScroll Whether to recenter even if the time is already
- * visible.
- *
- * @throws IllegalArgumentException of the provided date is before the
- * range start of after the range end.
- */
- private void goTo(Calendar date, boolean animate, boolean setSelected,
- boolean forceScroll) {
- if (date.before(mMinDate) || date.after(mMaxDate)) {
- throw new IllegalArgumentException("Time not between " + mMinDate.getTime()
- + " and " + mMaxDate.getTime());
- }
- // Find the first and last entirely visible weeks
- int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
- View firstChild = mListView.getChildAt(0);
- if (firstChild != null && firstChild.getTop() < 0) {
- firstFullyVisiblePosition++;
- }
- int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1;
- if (firstChild != null && firstChild.getTop() > mBottomBuffer) {
- lastFullyVisiblePosition--;
- }
- if (setSelected) {
- mAdapter.setSelectedDay(date);
- }
- // Get the week we're going to
- int position = getWeeksSinceMinDate(date);
-
- // Check if the selected day is now outside of our visible range
- // and if so scroll to the month that contains it
- if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition
- || forceScroll) {
- mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis());
- mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1);
-
- setMonthDisplayed(mFirstDayOfMonth);
-
- // the earliest time we can scroll to is the min date
- if (mFirstDayOfMonth.before(mMinDate)) {
- position = 0;
- } else {
- position = getWeeksSinceMinDate(mFirstDayOfMonth);
- }
-
- mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
- if (animate) {
- mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset,
- GOTO_SCROLL_DURATION);
- } else {
- mListView.setSelectionFromTop(position, mListScrollTopOffset);
- // Perform any after scroll operations that are needed
- onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE);
- }
- } else if (setSelected) {
- // Otherwise just set the selection
- setMonthDisplayed(date);
- }
- }
/**
* Parses the given <code>date</code> and in case of success sets
@@ -1435,718 +607,15 @@ public class CalendarView extends FrameLayout {
*
* @return True if the date was parsed.
*/
- private boolean parseDate(String date, Calendar outDate) {
+ protected boolean parseDate(String date, Calendar outDate) {
try {
- outDate.setTime(mDateFormat.parse(date));
+ outDate.setTime(DATE_FORMATTER.parse(date));
return true;
} catch (ParseException e) {
Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
return false;
}
}
-
- /**
- * Called when a <code>view</code> transitions to a new <code>scrollState
- * </code>.
- */
- private void onScrollStateChanged(AbsListView view, int scrollState) {
- mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
- }
-
- /**
- * Updates the title and selected month if the <code>view</code> has moved to a new
- * month.
- */
- private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
- int totalItemCount) {
- WeekView child = (WeekView) view.getChildAt(0);
- if (child == null) {
- return;
- }
-
- // Figure out where we are
- long currScroll =
- view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
-
- // If we have moved since our last call update the direction
- if (currScroll < mPreviousScrollPosition) {
- mIsScrollingUp = true;
- } else if (currScroll > mPreviousScrollPosition) {
- mIsScrollingUp = false;
- } else {
- return;
- }
-
- // Use some hysteresis for checking which month to highlight. This
- // causes the month to transition when two full weeks of a month are
- // visible when scrolling up, and when the first day in a month reaches
- // the top of the screen when scrolling down.
- int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0;
- if (mIsScrollingUp) {
- child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset);
- } else if (offset != 0) {
- child = (WeekView) view.getChildAt(offset);
- }
-
- if (child != null) {
- // Find out which month we're moving into
- int month;
- if (mIsScrollingUp) {
- month = child.getMonthOfFirstWeekDay();
- } else {
- month = child.getMonthOfLastWeekDay();
- }
-
- // And how it relates to our current highlighted month
- int monthDiff;
- if (mCurrentMonthDisplayed == 11 && month == 0) {
- monthDiff = 1;
- } else if (mCurrentMonthDisplayed == 0 && month == 11) {
- monthDiff = -1;
- } else {
- monthDiff = month - mCurrentMonthDisplayed;
- }
-
- // Only switch months if we're scrolling away from the currently
- // selected month
- if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) {
- Calendar firstDay = child.getFirstDay();
- if (mIsScrollingUp) {
- firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK);
- } else {
- firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK);
- }
- setMonthDisplayed(firstDay);
- }
- }
- mPreviousScrollPosition = currScroll;
- mPreviousScrollState = mCurrentScrollState;
- }
-
- /**
- * Sets the month displayed at the top of this view based on time. Override
- * to add custom events when the title is changed.
- *
- * @param calendar A day in the new focus month.
- */
- private void setMonthDisplayed(Calendar calendar) {
- mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
- mAdapter.setFocusMonth(mCurrentMonthDisplayed);
- final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
- | DateUtils.FORMAT_SHOW_YEAR;
- final long millis = calendar.getTimeInMillis();
- String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
- mMonthName.setText(newMonthName);
- mMonthName.invalidate();
- }
-
- /**
- * @return Returns the number of weeks between the current <code>date</code>
- * and the <code>mMinDate</code>.
- */
- private int getWeeksSinceMinDate(Calendar date) {
- if (date.before(mMinDate)) {
- throw new IllegalArgumentException("fromDate: " + mMinDate.getTime()
- + " does not precede toDate: " + date.getTime());
- }
- long endTimeMillis = date.getTimeInMillis()
- + date.getTimeZone().getOffset(date.getTimeInMillis());
- long startTimeMillis = mMinDate.getTimeInMillis()
- + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis());
- long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek)
- * MILLIS_IN_DAY;
- return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK);
- }
-
- /**
- * Command responsible for acting upon scroll state changes.
- */
- private class ScrollStateRunnable implements Runnable {
- private AbsListView mView;
-
- private int mNewState;
-
- /**
- * Sets up the runnable with a short delay in case the scroll state
- * immediately changes again.
- *
- * @param view The list view that changed state
- * @param scrollState The new state it changed to
- */
- public void doScrollStateChange(AbsListView view, int scrollState) {
- mView = view;
- mNewState = scrollState;
- mDelegator.removeCallbacks(this);
- mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY);
- }
-
- public void run() {
- mCurrentScrollState = mNewState;
- // Fix the position after a scroll or a fling ends
- if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
- && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
- View child = mView.getChildAt(0);
- if (child == null) {
- // The view is no longer visible, just return
- return;
- }
- int dist = child.getBottom() - mListScrollTopOffset;
- if (dist > mListScrollTopOffset) {
- if (mIsScrollingUp) {
- mView.smoothScrollBy(dist - child.getHeight(),
- ADJUSTMENT_SCROLL_DURATION);
- } else {
- mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION);
- }
- }
- }
- mPreviousScrollState = mNewState;
- }
- }
-
- /**
- * <p>
- * This is a specialized adapter for creating a list of weeks with
- * selectable days. It can be configured to display the week number, start
- * the week on a given day, show a reduced number of days, or display an
- * arbitrary number of weeks at a time.
- * </p>
- */
- private class WeeksAdapter extends BaseAdapter implements OnTouchListener {
-
- private int mSelectedWeek;
-
- private GestureDetector mGestureDetector;
-
- private int mFocusedMonth;
-
- private final Calendar mSelectedDate = Calendar.getInstance();
-
- private int mTotalWeekCount;
-
- public WeeksAdapter(Context context) {
- mContext = context;
- mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
- init();
- }
-
- /**
- * Set up the gesture detector and selected time
- */
- private void init() {
- mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
- mTotalWeekCount = getWeeksSinceMinDate(mMaxDate);
- if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek
- || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) {
- mTotalWeekCount++;
- }
- notifyDataSetChanged();
- }
-
- /**
- * Updates the selected day and related parameters.
- *
- * @param selectedDay The time to highlight
- */
- public void setSelectedDay(Calendar selectedDay) {
- if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR)
- && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) {
- return;
- }
- mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis());
- mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
- mFocusedMonth = mSelectedDate.get(Calendar.MONTH);
- notifyDataSetChanged();
- }
-
- /**
- * @return The selected day of month.
- */
- public Calendar getSelectedDay() {
- return mSelectedDate;
- }
-
- @Override
- public int getCount() {
- return mTotalWeekCount;
- }
-
- @Override
- public Object getItem(int position) {
- return null;
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- WeekView weekView = null;
- if (convertView != null) {
- weekView = (WeekView) convertView;
- } else {
- weekView = new WeekView(mContext);
- android.widget.AbsListView.LayoutParams params =
- new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT);
- weekView.setLayoutParams(params);
- weekView.setClickable(true);
- weekView.setOnTouchListener(this);
- }
-
- int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
- Calendar.DAY_OF_WEEK) : -1;
- weekView.init(position, selectedWeekDay, mFocusedMonth);
-
- return weekView;
- }
-
- /**
- * Changes which month is in focus and updates the view.
- *
- * @param month The month to show as in focus [0-11]
- */
- public void setFocusMonth(int month) {
- if (mFocusedMonth == month) {
- return;
- }
- mFocusedMonth = month;
- notifyDataSetChanged();
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) {
- WeekView weekView = (WeekView) v;
- // if we cannot find a day for the given location we are done
- if (!weekView.getDayFromLocation(event.getX(), mTempDate)) {
- return true;
- }
- // it is possible that the touched day is outside the valid range
- // we draw whole weeks but range end can fall not on the week end
- if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
- return true;
- }
- onDateTapped(mTempDate);
- return true;
- }
- return false;
- }
-
- /**
- * Maintains the same hour/min/sec but moves the day to the tapped day.
- *
- * @param day The day that was tapped
- */
- private void onDateTapped(Calendar day) {
- setSelectedDay(day);
- setMonthDisplayed(day);
- }
-
- /**
- * This is here so we can identify single tap events and set the
- * selected day correctly
- */
- class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- return true;
- }
- }
- }
-
- /**
- * <p>
- * This is a dynamic view for drawing a single week. It can be configured to
- * display the week number, start the week on a given day, or show a reduced
- * number of days. It is intended for use as a single view within a
- * ListView. See {@link WeeksAdapter} for usage.
- * </p>
- */
- private class WeekView extends View {
-
- private final Rect mTempRect = new Rect();
-
- private final Paint mDrawPaint = new Paint();
-
- private final Paint mMonthNumDrawPaint = new Paint();
-
- // Cache the number strings so we don't have to recompute them each time
- private String[] mDayNumbers;
-
- // Quick lookup for checking which days are in the focus month
- private boolean[] mFocusDay;
-
- // Whether this view has a focused day.
- private boolean mHasFocusedDay;
-
- // Whether this view has only focused days.
- private boolean mHasUnfocusedDay;
-
- // The first day displayed by this item
- private Calendar mFirstDay;
-
- // The month of the first day in this week
- private int mMonthOfFirstWeekDay = -1;
-
- // The month of the last day in this week
- private int mLastWeekDayMonth = -1;
-
- // The position of this week, equivalent to weeks since the week of Jan
- // 1st, 1900
- private int mWeek = -1;
-
- // Quick reference to the width of this view, matches parent
- private int mWidth;
-
- // The height this view should draw at in pixels, set by height param
- private int mHeight;
-
- // If this view contains the selected day
- private boolean mHasSelectedDay = false;
-
- // Which day is selected [0-6] or -1 if no day is selected
- private int mSelectedDay = -1;
-
- // The number of days + a spot for week number if it is displayed
- private int mNumCells;
-
- // The left edge of the selected day
- private int mSelectedLeft = -1;
-
- // The right edge of the selected day
- private int mSelectedRight = -1;
-
- public WeekView(Context context) {
- super(context);
-
- // Sets up any standard paints that will be used
- initilaizePaints();
- }
-
- /**
- * Initializes this week view.
- *
- * @param weekNumber The number of the week this view represents. The
- * week number is a zero based index of the weeks since
- * {@link CalendarView#getMinDate()}.
- * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no
- * selected day.
- * @param focusedMonth The month that is currently in focus i.e.
- * highlighted.
- */
- public void init(int weekNumber, int selectedWeekDay, int focusedMonth) {
- mSelectedDay = selectedWeekDay;
- mHasSelectedDay = mSelectedDay != -1;
- mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek;
- mWeek = weekNumber;
- mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
-
- mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek);
- mTempDate.setFirstDayOfWeek(mFirstDayOfWeek);
-
- // Allocate space for caching the day numbers and focus values
- mDayNumbers = new String[mNumCells];
- mFocusDay = new boolean[mNumCells];
-
- // If we're showing the week number calculate it based on Monday
- int i = 0;
- if (mShowWeekNumber) {
- mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
- mTempDate.get(Calendar.WEEK_OF_YEAR));
- i++;
- }
-
- // Now adjust our starting day based on the start day of the week
- int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK);
- mTempDate.add(Calendar.DAY_OF_MONTH, diff);
-
- mFirstDay = (Calendar) mTempDate.clone();
- mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH);
-
- mHasUnfocusedDay = true;
- for (; i < mNumCells; i++) {
- final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth);
- mFocusDay[i] = isFocusedDay;
- mHasFocusedDay |= isFocusedDay;
- mHasUnfocusedDay &= !isFocusedDay;
- // do not draw dates outside the valid range to avoid user confusion
- if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
- mDayNumbers[i] = "";
- } else {
- mDayNumbers[i] = String.format(Locale.getDefault(), "%d",
- mTempDate.get(Calendar.DAY_OF_MONTH));
- }
- mTempDate.add(Calendar.DAY_OF_MONTH, 1);
- }
- // We do one extra add at the end of the loop, if that pushed us to
- // new month undo it
- if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) {
- mTempDate.add(Calendar.DAY_OF_MONTH, -1);
- }
- mLastWeekDayMonth = mTempDate.get(Calendar.MONTH);
-
- updateSelectionPositions();
- }
-
- /**
- * Initialize the paint instances.
- */
- private void initilaizePaints() {
- mDrawPaint.setFakeBoldText(false);
- mDrawPaint.setAntiAlias(true);
- mDrawPaint.setStyle(Style.FILL);
-
- mMonthNumDrawPaint.setFakeBoldText(true);
- mMonthNumDrawPaint.setAntiAlias(true);
- mMonthNumDrawPaint.setStyle(Style.FILL);
- mMonthNumDrawPaint.setTextAlign(Align.CENTER);
- mMonthNumDrawPaint.setTextSize(mDateTextSize);
- }
-
- /**
- * Returns the month of the first day in this week.
- *
- * @return The month the first day of this view is in.
- */
- public int getMonthOfFirstWeekDay() {
- return mMonthOfFirstWeekDay;
- }
-
- /**
- * Returns the month of the last day in this week
- *
- * @return The month the last day of this view is in
- */
- public int getMonthOfLastWeekDay() {
- return mLastWeekDayMonth;
- }
-
- /**
- * Returns the first day in this view.
- *
- * @return The first day in the view.
- */
- public Calendar getFirstDay() {
- return mFirstDay;
- }
-
- /**
- * Calculates the day that the given x position is in, accounting for
- * week number.
- *
- * @param x The x position of the touch event.
- * @return True if a day was found for the given location.
- */
- public boolean getDayFromLocation(float x, Calendar outCalendar) {
- final boolean isLayoutRtl = isLayoutRtl();
-
- int start;
- int end;
-
- if (isLayoutRtl) {
- start = 0;
- end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
- } else {
- start = mShowWeekNumber ? mWidth / mNumCells : 0;
- end = mWidth;
- }
-
- if (x < start || x > end) {
- outCalendar.clear();
- return false;
- }
-
- // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
- int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
-
- if (isLayoutRtl) {
- dayPosition = mDaysPerWeek - 1 - dayPosition;
- }
-
- outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
- outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
-
- return true;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- drawBackground(canvas);
- drawWeekNumbersAndDates(canvas);
- drawWeekSeparators(canvas);
- drawSelectedDateVerticalBars(canvas);
- }
-
- /**
- * This draws the selection highlight if a day is selected in this week.
- *
- * @param canvas The canvas to draw on
- */
- private void drawBackground(Canvas canvas) {
- if (!mHasSelectedDay) {
- return;
- }
- mDrawPaint.setColor(mSelectedWeekBackgroundColor);
-
- mTempRect.top = mWeekSeperatorLineWidth;
- mTempRect.bottom = mHeight;
-
- final boolean isLayoutRtl = isLayoutRtl();
-
- if (isLayoutRtl) {
- mTempRect.left = 0;
- mTempRect.right = mSelectedLeft - 2;
- } else {
- mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
- mTempRect.right = mSelectedLeft - 2;
- }
- canvas.drawRect(mTempRect, mDrawPaint);
-
- if (isLayoutRtl) {
- mTempRect.left = mSelectedRight + 3;
- mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
- } else {
- mTempRect.left = mSelectedRight + 3;
- mTempRect.right = mWidth;
- }
- canvas.drawRect(mTempRect, mDrawPaint);
- }
-
- /**
- * Draws the week and month day numbers for this week.
- *
- * @param canvas The canvas to draw on
- */
- private void drawWeekNumbersAndDates(Canvas canvas) {
- final float textHeight = mDrawPaint.getTextSize();
- final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
- final int nDays = mNumCells;
- final int divisor = 2 * nDays;
-
- mDrawPaint.setTextAlign(Align.CENTER);
- mDrawPaint.setTextSize(mDateTextSize);
-
- int i = 0;
-
- if (isLayoutRtl()) {
- for (; i < nDays - 1; i++) {
- mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
- : mUnfocusedMonthDateColor);
- int x = (2 * i + 1) * mWidth / divisor;
- canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint);
- }
- if (mShowWeekNumber) {
- mDrawPaint.setColor(mWeekNumberColor);
- int x = mWidth - mWidth / divisor;
- canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
- }
- } else {
- if (mShowWeekNumber) {
- mDrawPaint.setColor(mWeekNumberColor);
- int x = mWidth / divisor;
- canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
- i++;
- }
- for (; i < nDays; i++) {
- mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
- : mUnfocusedMonthDateColor);
- int x = (2 * i + 1) * mWidth / divisor;
- canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
- }
- }
- }
-
- /**
- * Draws a horizontal line for separating the weeks.
- *
- * @param canvas The canvas to draw on.
- */
- private void drawWeekSeparators(Canvas canvas) {
- // If it is the topmost fully visible child do not draw separator line
- int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
- if (mListView.getChildAt(0).getTop() < 0) {
- firstFullyVisiblePosition++;
- }
- if (firstFullyVisiblePosition == mWeek) {
- return;
- }
- mDrawPaint.setColor(mWeekSeparatorLineColor);
- mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth);
- float startX;
- float stopX;
- if (isLayoutRtl()) {
- startX = 0;
- stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
- } else {
- startX = mShowWeekNumber ? mWidth / mNumCells : 0;
- stopX = mWidth;
- }
- canvas.drawLine(startX, 0, stopX, 0, mDrawPaint);
- }
-
- /**
- * Draws the selected date bars if this week has a selected day.
- *
- * @param canvas The canvas to draw on
- */
- private void drawSelectedDateVerticalBars(Canvas canvas) {
- if (!mHasSelectedDay) {
- return;
- }
- mSelectedDateVerticalBar.setBounds(
- mSelectedLeft - mSelectedDateVerticalBarWidth / 2,
- mWeekSeperatorLineWidth,
- mSelectedLeft + mSelectedDateVerticalBarWidth / 2,
- mHeight);
- mSelectedDateVerticalBar.draw(canvas);
- mSelectedDateVerticalBar.setBounds(
- mSelectedRight - mSelectedDateVerticalBarWidth / 2,
- mWeekSeperatorLineWidth,
- mSelectedRight + mSelectedDateVerticalBarWidth / 2,
- mHeight);
- mSelectedDateVerticalBar.draw(canvas);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- mWidth = w;
- updateSelectionPositions();
- }
-
- /**
- * This calculates the positions for the selected day lines.
- */
- private void updateSelectionPositions() {
- if (mHasSelectedDay) {
- final boolean isLayoutRtl = isLayoutRtl();
- int selectedPosition = mSelectedDay - mFirstDayOfWeek;
- if (selectedPosition < 0) {
- selectedPosition += 7;
- }
- if (mShowWeekNumber && !isLayoutRtl) {
- selectedPosition++;
- }
- if (isLayoutRtl) {
- mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
-
- } else {
- mSelectedLeft = selectedPosition * mWidth / mNumCells;
- }
- mSelectedRight = mSelectedLeft + mWidth / mNumCells;
- }
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView
- .getPaddingBottom()) / mShownWeekCount;
- setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
- }
- }
-
}
}
diff --git a/core/java/android/widget/CalendarViewLegacyDelegate.java b/core/java/android/widget/CalendarViewLegacyDelegate.java
new file mode 100644
index 0000000..2ab3548
--- /dev/null
+++ b/core/java/android/widget/CalendarViewLegacyDelegate.java
@@ -0,0 +1,1527 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import com.android.internal.R;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.GestureDetector;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.Calendar;
+import java.util.Locale;
+
+import libcore.icu.LocaleData;
+
+/**
+ * A delegate implementing the legacy CalendarView
+ */
+class CalendarViewLegacyDelegate extends CalendarView.AbstractCalendarViewDelegate {
+ /**
+ * Default value whether to show week number.
+ */
+ private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true;
+
+ /**
+ * The number of milliseconds in a day.e
+ */
+ private static final long MILLIS_IN_DAY = 86400000L;
+
+ /**
+ * The number of day in a week.
+ */
+ private static final int DAYS_PER_WEEK = 7;
+
+ /**
+ * The number of milliseconds in a week.
+ */
+ private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY;
+
+ /**
+ * Affects when the month selection will change while scrolling upe
+ */
+ private static final int SCROLL_HYST_WEEKS = 2;
+
+ /**
+ * How long the GoTo fling animation should last.
+ */
+ private static final int GOTO_SCROLL_DURATION = 1000;
+
+ /**
+ * The duration of the adjustment upon a user scroll in milliseconds.
+ */
+ private static final int ADJUSTMENT_SCROLL_DURATION = 500;
+
+ /**
+ * How long to wait after receiving an onScrollStateChanged notification
+ * before acting on it.
+ */
+ private static final int SCROLL_CHANGE_DELAY = 40;
+
+ private static final int DEFAULT_SHOWN_WEEK_COUNT = 6;
+
+ private static final int DEFAULT_DATE_TEXT_SIZE = 14;
+
+ private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6;
+
+ private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12;
+
+ private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2;
+
+ private static final int UNSCALED_BOTTOM_BUFFER = 20;
+
+ private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1;
+
+ private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1;
+
+ private final int mWeekSeperatorLineWidth;
+
+ private int mDateTextSize;
+
+ private Drawable mSelectedDateVerticalBar;
+
+ private final int mSelectedDateVerticalBarWidth;
+
+ private int mSelectedWeekBackgroundColor;
+
+ private int mFocusedMonthDateColor;
+
+ private int mUnfocusedMonthDateColor;
+
+ private int mWeekSeparatorLineColor;
+
+ private int mWeekNumberColor;
+
+ private int mWeekDayTextAppearanceResId;
+
+ private int mDateTextAppearanceResId;
+
+ /**
+ * The top offset of the weeks list.
+ */
+ private int mListScrollTopOffset = 2;
+
+ /**
+ * The visible height of a week view.
+ */
+ private int mWeekMinVisibleHeight = 12;
+
+ /**
+ * The visible height of a week view.
+ */
+ private int mBottomBuffer = 20;
+
+ /**
+ * The number of shown weeks.
+ */
+ private int mShownWeekCount;
+
+ /**
+ * Flag whether to show the week number.
+ */
+ private boolean mShowWeekNumber;
+
+ /**
+ * The number of day per week to be shown.
+ */
+ private int mDaysPerWeek = 7;
+
+ /**
+ * The friction of the week list while flinging.
+ */
+ private float mFriction = .05f;
+
+ /**
+ * Scale for adjusting velocity of the week list while flinging.
+ */
+ private float mVelocityScale = 0.333f;
+
+ /**
+ * The adapter for the weeks list.
+ */
+ private WeeksAdapter mAdapter;
+
+ /**
+ * The weeks list.
+ */
+ private ListView mListView;
+
+ /**
+ * The name of the month to display.
+ */
+ private TextView mMonthName;
+
+ /**
+ * The header with week day names.
+ */
+ private ViewGroup mDayNamesHeader;
+
+ /**
+ * Cached abbreviations for day of week names.
+ */
+ private String[] mDayNamesShort;
+
+ /**
+ * Cached full-length day of week names.
+ */
+ private String[] mDayNamesLong;
+
+ /**
+ * The first day of the week.
+ */
+ private int mFirstDayOfWeek;
+
+ /**
+ * Which month should be displayed/highlighted [0-11].
+ */
+ private int mCurrentMonthDisplayed = -1;
+
+ /**
+ * Used for tracking during a scroll.
+ */
+ private long mPreviousScrollPosition;
+
+ /**
+ * Used for tracking which direction the view is scrolling.
+ */
+ private boolean mIsScrollingUp = false;
+
+ /**
+ * The previous scroll state of the weeks ListView.
+ */
+ private int mPreviousScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE;
+
+ /**
+ * The current scroll state of the weeks ListView.
+ */
+ private int mCurrentScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE;
+
+ /**
+ * Listener for changes in the selected day.
+ */
+ private CalendarView.OnDateChangeListener mOnDateChangeListener;
+
+ /**
+ * Command for adjusting the position after a scroll/fling.
+ */
+ private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
+
+ /**
+ * Temporary instance to avoid multiple instantiations.
+ */
+ private Calendar mTempDate;
+
+ /**
+ * The first day of the focused month.
+ */
+ private Calendar mFirstDayOfMonth;
+
+ /**
+ * The start date of the range supported by this picker.
+ */
+ private Calendar mMinDate;
+
+ /**
+ * The end date of the range supported by this picker.
+ */
+ private Calendar mMaxDate;
+
+ CalendarViewLegacyDelegate(CalendarView delegator, Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(delegator, context);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.CalendarView, defStyleAttr, defStyleRes);
+ mShowWeekNumber = a.getBoolean(R.styleable.CalendarView_showWeekNumber,
+ DEFAULT_SHOW_WEEK_NUMBER);
+ mFirstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek,
+ LocaleData.get(Locale.getDefault()).firstDayOfWeek);
+ final String minDate = a.getString(R.styleable.CalendarView_minDate);
+ if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) {
+ parseDate(DEFAULT_MIN_DATE, mMinDate);
+ }
+ final String maxDate = a.getString(R.styleable.CalendarView_maxDate);
+ if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) {
+ parseDate(DEFAULT_MAX_DATE, mMaxDate);
+ }
+ if (mMaxDate.before(mMinDate)) {
+ throw new IllegalArgumentException("Max date cannot be before min date.");
+ }
+ mShownWeekCount = a.getInt(R.styleable.CalendarView_shownWeekCount,
+ DEFAULT_SHOWN_WEEK_COUNT);
+ mSelectedWeekBackgroundColor = a.getColor(
+ R.styleable.CalendarView_selectedWeekBackgroundColor, 0);
+ mFocusedMonthDateColor = a.getColor(
+ R.styleable.CalendarView_focusedMonthDateColor, 0);
+ mUnfocusedMonthDateColor = a.getColor(
+ R.styleable.CalendarView_unfocusedMonthDateColor, 0);
+ mWeekSeparatorLineColor = a.getColor(
+ R.styleable.CalendarView_weekSeparatorLineColor, 0);
+ mWeekNumberColor = a.getColor(R.styleable.CalendarView_weekNumberColor, 0);
+ mSelectedDateVerticalBar = a.getDrawable(
+ R.styleable.CalendarView_selectedDateVerticalBar);
+
+ mDateTextAppearanceResId = a.getResourceId(
+ R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small);
+ updateDateTextSize();
+
+ mWeekDayTextAppearanceResId = a.getResourceId(
+ R.styleable.CalendarView_weekDayTextAppearance,
+ DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID);
+ a.recycle();
+
+ DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics();
+ mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics);
+ mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics);
+ mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_BOTTOM_BUFFER, displayMetrics);
+ mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics);
+ mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics);
+
+ LayoutInflater layoutInflater = (LayoutInflater) mContext
+ .getSystemService(Service.LAYOUT_INFLATER_SERVICE);
+ View content = layoutInflater.inflate(R.layout.calendar_view, null, false);
+ mDelegator.addView(content);
+
+ mListView = (ListView) mDelegator.findViewById(R.id.list);
+ mDayNamesHeader = (ViewGroup) content.findViewById(R.id.day_names);
+ mMonthName = (TextView) content.findViewById(R.id.month_name);
+
+ setUpHeader();
+ setUpListView();
+ setUpAdapter();
+
+ // go to today or whichever is close to today min or max date
+ mTempDate.setTimeInMillis(System.currentTimeMillis());
+ if (mTempDate.before(mMinDate)) {
+ goTo(mMinDate, false, true, true);
+ } else if (mMaxDate.before(mTempDate)) {
+ goTo(mMaxDate, false, true, true);
+ } else {
+ goTo(mTempDate, false, true, true);
+ }
+
+ mDelegator.invalidate();
+ }
+
+ @Override
+ public void setShownWeekCount(int count) {
+ if (mShownWeekCount != count) {
+ mShownWeekCount = count;
+ mDelegator.invalidate();
+ }
+ }
+
+ @Override
+ public int getShownWeekCount() {
+ return mShownWeekCount;
+ }
+
+ @Override
+ public void setSelectedWeekBackgroundColor(int color) {
+ if (mSelectedWeekBackgroundColor != color) {
+ mSelectedWeekBackgroundColor = color;
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ WeekView weekView = (WeekView) mListView.getChildAt(i);
+ if (weekView.mHasSelectedDay) {
+ weekView.invalidate();
+ }
+ }
+ }
+ }
+
+ @Override
+ public int getSelectedWeekBackgroundColor() {
+ return mSelectedWeekBackgroundColor;
+ }
+
+ @Override
+ public void setFocusedMonthDateColor(int color) {
+ if (mFocusedMonthDateColor != color) {
+ mFocusedMonthDateColor = color;
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ WeekView weekView = (WeekView) mListView.getChildAt(i);
+ if (weekView.mHasFocusedDay) {
+ weekView.invalidate();
+ }
+ }
+ }
+ }
+
+ @Override
+ public int getFocusedMonthDateColor() {
+ return mFocusedMonthDateColor;
+ }
+
+ @Override
+ public void setUnfocusedMonthDateColor(int color) {
+ if (mUnfocusedMonthDateColor != color) {
+ mUnfocusedMonthDateColor = color;
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ WeekView weekView = (WeekView) mListView.getChildAt(i);
+ if (weekView.mHasUnfocusedDay) {
+ weekView.invalidate();
+ }
+ }
+ }
+ }
+
+ @Override
+ public int getUnfocusedMonthDateColor() {
+ return mFocusedMonthDateColor;
+ }
+
+ @Override
+ public void setWeekNumberColor(int color) {
+ if (mWeekNumberColor != color) {
+ mWeekNumberColor = color;
+ if (mShowWeekNumber) {
+ invalidateAllWeekViews();
+ }
+ }
+ }
+
+ @Override
+ public int getWeekNumberColor() {
+ return mWeekNumberColor;
+ }
+
+ @Override
+ public void setWeekSeparatorLineColor(int color) {
+ if (mWeekSeparatorLineColor != color) {
+ mWeekSeparatorLineColor = color;
+ invalidateAllWeekViews();
+ }
+ }
+
+ @Override
+ public int getWeekSeparatorLineColor() {
+ return mWeekSeparatorLineColor;
+ }
+
+ @Override
+ public void setSelectedDateVerticalBar(int resourceId) {
+ Drawable drawable = mDelegator.getContext().getDrawable(resourceId);
+ setSelectedDateVerticalBar(drawable);
+ }
+
+ @Override
+ public void setSelectedDateVerticalBar(Drawable drawable) {
+ if (mSelectedDateVerticalBar != drawable) {
+ mSelectedDateVerticalBar = drawable;
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ WeekView weekView = (WeekView) mListView.getChildAt(i);
+ if (weekView.mHasSelectedDay) {
+ weekView.invalidate();
+ }
+ }
+ }
+ }
+
+ @Override
+ public Drawable getSelectedDateVerticalBar() {
+ return mSelectedDateVerticalBar;
+ }
+
+ @Override
+ public void setWeekDayTextAppearance(int resourceId) {
+ if (mWeekDayTextAppearanceResId != resourceId) {
+ mWeekDayTextAppearanceResId = resourceId;
+ setUpHeader();
+ }
+ }
+
+ @Override
+ public int getWeekDayTextAppearance() {
+ return mWeekDayTextAppearanceResId;
+ }
+
+ @Override
+ public void setDateTextAppearance(int resourceId) {
+ if (mDateTextAppearanceResId != resourceId) {
+ mDateTextAppearanceResId = resourceId;
+ updateDateTextSize();
+ invalidateAllWeekViews();
+ }
+ }
+
+ @Override
+ public int getDateTextAppearance() {
+ return mDateTextAppearanceResId;
+ }
+
+ @Override
+ public void setMinDate(long minDate) {
+ mTempDate.setTimeInMillis(minDate);
+ if (isSameDate(mTempDate, mMinDate)) {
+ return;
+ }
+ mMinDate.setTimeInMillis(minDate);
+ // make sure the current date is not earlier than
+ // the new min date since the latter is used for
+ // calculating the indices in the adapter thus
+ // avoiding out of bounds error
+ Calendar date = mAdapter.mSelectedDate;
+ if (date.before(mMinDate)) {
+ mAdapter.setSelectedDay(mMinDate);
+ }
+ // reinitialize the adapter since its range depends on min date
+ mAdapter.init();
+ if (date.before(mMinDate)) {
+ setDate(mTempDate.getTimeInMillis());
+ } else {
+ // we go to the current date to force the ListView to query its
+ // adapter for the shown views since we have changed the adapter
+ // range and the base from which the later calculates item indices
+ // note that calling setDate will not work since the date is the same
+ goTo(date, false, true, false);
+ }
+ }
+
+ @Override
+ public long getMinDate() {
+ return mMinDate.getTimeInMillis();
+ }
+
+ @Override
+ public void setMaxDate(long maxDate) {
+ mTempDate.setTimeInMillis(maxDate);
+ if (isSameDate(mTempDate, mMaxDate)) {
+ return;
+ }
+ mMaxDate.setTimeInMillis(maxDate);
+ // reinitialize the adapter since its range depends on max date
+ mAdapter.init();
+ Calendar date = mAdapter.mSelectedDate;
+ if (date.after(mMaxDate)) {
+ setDate(mMaxDate.getTimeInMillis());
+ } else {
+ // we go to the current date to force the ListView to query its
+ // adapter for the shown views since we have changed the adapter
+ // range and the base from which the later calculates item indices
+ // note that calling setDate will not work since the date is the same
+ goTo(date, false, true, false);
+ }
+ }
+
+ @Override
+ public long getMaxDate() {
+ return mMaxDate.getTimeInMillis();
+ }
+
+ @Override
+ public void setShowWeekNumber(boolean showWeekNumber) {
+ if (mShowWeekNumber == showWeekNumber) {
+ return;
+ }
+ mShowWeekNumber = showWeekNumber;
+ mAdapter.notifyDataSetChanged();
+ setUpHeader();
+ }
+
+ @Override
+ public boolean getShowWeekNumber() {
+ return mShowWeekNumber;
+ }
+
+ @Override
+ public void setFirstDayOfWeek(int firstDayOfWeek) {
+ if (mFirstDayOfWeek == firstDayOfWeek) {
+ return;
+ }
+ mFirstDayOfWeek = firstDayOfWeek;
+ mAdapter.init();
+ mAdapter.notifyDataSetChanged();
+ setUpHeader();
+ }
+
+ @Override
+ public int getFirstDayOfWeek() {
+ return mFirstDayOfWeek;
+ }
+
+ @Override
+ public void setDate(long date) {
+ setDate(date, false, false);
+ }
+
+ @Override
+ public void setDate(long date, boolean animate, boolean center) {
+ mTempDate.setTimeInMillis(date);
+ if (isSameDate(mTempDate, mAdapter.mSelectedDate)) {
+ return;
+ }
+ goTo(mTempDate, animate, true, center);
+ }
+
+ @Override
+ public long getDate() {
+ return mAdapter.mSelectedDate.getTimeInMillis();
+ }
+
+ @Override
+ public void setOnDateChangeListener(CalendarView.OnDateChangeListener listener) {
+ mOnDateChangeListener = listener;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ setCurrentLocale(newConfig.locale);
+ }
+
+ /**
+ * Sets the current locale.
+ *
+ * @param locale The current locale.
+ */
+ @Override
+ protected void setCurrentLocale(Locale locale) {
+ super.setCurrentLocale(locale);
+
+ mTempDate = getCalendarForLocale(mTempDate, locale);
+ mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale);
+ mMinDate = getCalendarForLocale(mMinDate, locale);
+ mMaxDate = getCalendarForLocale(mMaxDate, locale);
+ }
+ private void updateDateTextSize() {
+ TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes(
+ mDateTextAppearanceResId, R.styleable.TextAppearance);
+ mDateTextSize = dateTextAppearance.getDimensionPixelSize(
+ R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE);
+ dateTextAppearance.recycle();
+ }
+
+ /**
+ * Invalidates all week views.
+ */
+ private void invalidateAllWeekViews() {
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View view = mListView.getChildAt(i);
+ view.invalidate();
+ }
+ }
+
+ /**
+ * Gets a calendar for locale bootstrapped with the value of a given calendar.
+ *
+ * @param oldCalendar The old calendar.
+ * @param locale The locale.
+ */
+ private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
+ if (oldCalendar == null) {
+ return Calendar.getInstance(locale);
+ } else {
+ final long currentTimeMillis = oldCalendar.getTimeInMillis();
+ Calendar newCalendar = Calendar.getInstance(locale);
+ newCalendar.setTimeInMillis(currentTimeMillis);
+ return newCalendar;
+ }
+ }
+
+ /**
+ * @return True if the <code>firstDate</code> is the same as the <code>
+ * secondDate</code>.
+ */
+ private static boolean isSameDate(Calendar firstDate, Calendar secondDate) {
+ return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR)
+ && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR));
+ }
+
+ /**
+ * Creates a new adapter if necessary and sets up its parameters.
+ */
+ private void setUpAdapter() {
+ if (mAdapter == null) {
+ mAdapter = new WeeksAdapter(mContext);
+ mAdapter.registerDataSetObserver(new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ if (mOnDateChangeListener != null) {
+ Calendar selectedDay = mAdapter.getSelectedDay();
+ mOnDateChangeListener.onSelectedDayChange(mDelegator,
+ selectedDay.get(Calendar.YEAR),
+ selectedDay.get(Calendar.MONTH),
+ selectedDay.get(Calendar.DAY_OF_MONTH));
+ }
+ }
+ });
+ mListView.setAdapter(mAdapter);
+ }
+
+ // refresh the view with the new parameters
+ mAdapter.notifyDataSetChanged();
+ }
+
+ /**
+ * Sets up the strings to be used by the header.
+ */
+ private void setUpHeader() {
+ mDayNamesShort = new String[mDaysPerWeek];
+ mDayNamesLong = new String[mDaysPerWeek];
+ for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) {
+ int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i;
+ mDayNamesShort[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
+ DateUtils.LENGTH_SHORTEST);
+ mDayNamesLong[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
+ DateUtils.LENGTH_LONG);
+ }
+
+ TextView label = (TextView) mDayNamesHeader.getChildAt(0);
+ if (mShowWeekNumber) {
+ label.setVisibility(View.VISIBLE);
+ } else {
+ label.setVisibility(View.GONE);
+ }
+ for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) {
+ label = (TextView) mDayNamesHeader.getChildAt(i);
+ if (mWeekDayTextAppearanceResId > -1) {
+ label.setTextAppearance(mContext, mWeekDayTextAppearanceResId);
+ }
+ if (i < mDaysPerWeek + 1) {
+ label.setText(mDayNamesShort[i - 1]);
+ label.setContentDescription(mDayNamesLong[i - 1]);
+ label.setVisibility(View.VISIBLE);
+ } else {
+ label.setVisibility(View.GONE);
+ }
+ }
+ mDayNamesHeader.invalidate();
+ }
+
+ /**
+ * Sets all the required fields for the list view.
+ */
+ private void setUpListView() {
+ // Configure the listview
+ mListView.setDivider(null);
+ mListView.setItemsCanFocus(true);
+ mListView.setVerticalScrollBarEnabled(false);
+ mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ CalendarViewLegacyDelegate.this.onScrollStateChanged(view, scrollState);
+ }
+
+ public void onScroll(
+ AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ CalendarViewLegacyDelegate.this.onScroll(view, firstVisibleItem,
+ visibleItemCount, totalItemCount);
+ }
+ });
+ // Make the scrolling behavior nicer
+ mListView.setFriction(mFriction);
+ mListView.setVelocityScale(mVelocityScale);
+ }
+
+ /**
+ * This moves to the specified time in the view. If the time is not already
+ * in range it will move the list so that the first of the month containing
+ * the time is at the top of the view. If the new time is already in view
+ * the list will not be scrolled unless forceScroll is true. This time may
+ * optionally be highlighted as selected as well.
+ *
+ * @param date The time to move to.
+ * @param animate Whether to scroll to the given time or just redraw at the
+ * new location.
+ * @param setSelected Whether to set the given time as selected.
+ * @param forceScroll Whether to recenter even if the time is already
+ * visible.
+ *
+ * @throws IllegalArgumentException of the provided date is before the
+ * range start of after the range end.
+ */
+ private void goTo(Calendar date, boolean animate, boolean setSelected,
+ boolean forceScroll) {
+ if (date.before(mMinDate) || date.after(mMaxDate)) {
+ throw new IllegalArgumentException("Time not between " + mMinDate.getTime()
+ + " and " + mMaxDate.getTime());
+ }
+ // Find the first and last entirely visible weeks
+ int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
+ View firstChild = mListView.getChildAt(0);
+ if (firstChild != null && firstChild.getTop() < 0) {
+ firstFullyVisiblePosition++;
+ }
+ int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1;
+ if (firstChild != null && firstChild.getTop() > mBottomBuffer) {
+ lastFullyVisiblePosition--;
+ }
+ if (setSelected) {
+ mAdapter.setSelectedDay(date);
+ }
+ // Get the week we're going to
+ int position = getWeeksSinceMinDate(date);
+
+ // Check if the selected day is now outside of our visible range
+ // and if so scroll to the month that contains it
+ if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition
+ || forceScroll) {
+ mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis());
+ mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1);
+
+ setMonthDisplayed(mFirstDayOfMonth);
+
+ // the earliest time we can scroll to is the min date
+ if (mFirstDayOfMonth.before(mMinDate)) {
+ position = 0;
+ } else {
+ position = getWeeksSinceMinDate(mFirstDayOfMonth);
+ }
+
+ mPreviousScrollState = AbsListView.OnScrollListener.SCROLL_STATE_FLING;
+ if (animate) {
+ mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset,
+ GOTO_SCROLL_DURATION);
+ } else {
+ mListView.setSelectionFromTop(position, mListScrollTopOffset);
+ // Perform any after scroll operations that are needed
+ onScrollStateChanged(mListView, AbsListView.OnScrollListener.SCROLL_STATE_IDLE);
+ }
+ } else if (setSelected) {
+ // Otherwise just set the selection
+ setMonthDisplayed(date);
+ }
+ }
+
+ /**
+ * Called when a <code>view</code> transitions to a new <code>scrollState
+ * </code>.
+ */
+ private void onScrollStateChanged(AbsListView view, int scrollState) {
+ mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
+ }
+
+ /**
+ * Updates the title and selected month if the <code>view</code> has moved to a new
+ * month.
+ */
+ private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ WeekView child = (WeekView) view.getChildAt(0);
+ if (child == null) {
+ return;
+ }
+
+ // Figure out where we are
+ long currScroll =
+ view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
+
+ // If we have moved since our last call update the direction
+ if (currScroll < mPreviousScrollPosition) {
+ mIsScrollingUp = true;
+ } else if (currScroll > mPreviousScrollPosition) {
+ mIsScrollingUp = false;
+ } else {
+ return;
+ }
+
+ // Use some hysteresis for checking which month to highlight. This
+ // causes the month to transition when two full weeks of a month are
+ // visible when scrolling up, and when the first day in a month reaches
+ // the top of the screen when scrolling down.
+ int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0;
+ if (mIsScrollingUp) {
+ child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset);
+ } else if (offset != 0) {
+ child = (WeekView) view.getChildAt(offset);
+ }
+
+ if (child != null) {
+ // Find out which month we're moving into
+ int month;
+ if (mIsScrollingUp) {
+ month = child.getMonthOfFirstWeekDay();
+ } else {
+ month = child.getMonthOfLastWeekDay();
+ }
+
+ // And how it relates to our current highlighted month
+ int monthDiff;
+ if (mCurrentMonthDisplayed == 11 && month == 0) {
+ monthDiff = 1;
+ } else if (mCurrentMonthDisplayed == 0 && month == 11) {
+ monthDiff = -1;
+ } else {
+ monthDiff = month - mCurrentMonthDisplayed;
+ }
+
+ // Only switch months if we're scrolling away from the currently
+ // selected month
+ if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) {
+ Calendar firstDay = child.getFirstDay();
+ if (mIsScrollingUp) {
+ firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK);
+ } else {
+ firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK);
+ }
+ setMonthDisplayed(firstDay);
+ }
+ }
+ mPreviousScrollPosition = currScroll;
+ mPreviousScrollState = mCurrentScrollState;
+ }
+
+ /**
+ * Sets the month displayed at the top of this view based on time. Override
+ * to add custom events when the title is changed.
+ *
+ * @param calendar A day in the new focus month.
+ */
+ private void setMonthDisplayed(Calendar calendar) {
+ mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
+ mAdapter.setFocusMonth(mCurrentMonthDisplayed);
+ final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
+ | DateUtils.FORMAT_SHOW_YEAR;
+ final long millis = calendar.getTimeInMillis();
+ String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
+ mMonthName.setText(newMonthName);
+ mMonthName.invalidate();
+ }
+
+ /**
+ * @return Returns the number of weeks between the current <code>date</code>
+ * and the <code>mMinDate</code>.
+ */
+ private int getWeeksSinceMinDate(Calendar date) {
+ if (date.before(mMinDate)) {
+ throw new IllegalArgumentException("fromDate: " + mMinDate.getTime()
+ + " does not precede toDate: " + date.getTime());
+ }
+ long endTimeMillis = date.getTimeInMillis()
+ + date.getTimeZone().getOffset(date.getTimeInMillis());
+ long startTimeMillis = mMinDate.getTimeInMillis()
+ + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis());
+ long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek)
+ * MILLIS_IN_DAY;
+ return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK);
+ }
+
+ /**
+ * Command responsible for acting upon scroll state changes.
+ */
+ private class ScrollStateRunnable implements Runnable {
+ private AbsListView mView;
+
+ private int mNewState;
+
+ /**
+ * Sets up the runnable with a short delay in case the scroll state
+ * immediately changes again.
+ *
+ * @param view The list view that changed state
+ * @param scrollState The new state it changed to
+ */
+ public void doScrollStateChange(AbsListView view, int scrollState) {
+ mView = view;
+ mNewState = scrollState;
+ mDelegator.removeCallbacks(this);
+ mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY);
+ }
+
+ public void run() {
+ mCurrentScrollState = mNewState;
+ // Fix the position after a scroll or a fling ends
+ if (mNewState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE
+ && mPreviousScrollState != AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
+ View child = mView.getChildAt(0);
+ if (child == null) {
+ // The view is no longer visible, just return
+ return;
+ }
+ int dist = child.getBottom() - mListScrollTopOffset;
+ if (dist > mListScrollTopOffset) {
+ if (mIsScrollingUp) {
+ mView.smoothScrollBy(dist - child.getHeight(),
+ ADJUSTMENT_SCROLL_DURATION);
+ } else {
+ mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION);
+ }
+ }
+ }
+ mPreviousScrollState = mNewState;
+ }
+ }
+
+ /**
+ * <p>
+ * This is a specialized adapter for creating a list of weeks with
+ * selectable days. It can be configured to display the week number, start
+ * the week on a given day, show a reduced number of days, or display an
+ * arbitrary number of weeks at a time.
+ * </p>
+ */
+ private class WeeksAdapter extends BaseAdapter implements View.OnTouchListener {
+
+ private int mSelectedWeek;
+
+ private GestureDetector mGestureDetector;
+
+ private int mFocusedMonth;
+
+ private final Calendar mSelectedDate = Calendar.getInstance();
+
+ private int mTotalWeekCount;
+
+ public WeeksAdapter(Context context) {
+ mContext = context;
+ mGestureDetector = new GestureDetector(mContext, new WeeksAdapter.CalendarGestureListener());
+ init();
+ }
+
+ /**
+ * Set up the gesture detector and selected time
+ */
+ private void init() {
+ mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
+ mTotalWeekCount = getWeeksSinceMinDate(mMaxDate);
+ if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek
+ || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) {
+ mTotalWeekCount++;
+ }
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Updates the selected day and related parameters.
+ *
+ * @param selectedDay The time to highlight
+ */
+ public void setSelectedDay(Calendar selectedDay) {
+ if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR)
+ && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) {
+ return;
+ }
+ mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis());
+ mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
+ mFocusedMonth = mSelectedDate.get(Calendar.MONTH);
+ notifyDataSetChanged();
+ }
+
+ /**
+ * @return The selected day of month.
+ */
+ public Calendar getSelectedDay() {
+ return mSelectedDate;
+ }
+
+ @Override
+ public int getCount() {
+ return mTotalWeekCount;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ WeekView weekView = null;
+ if (convertView != null) {
+ weekView = (WeekView) convertView;
+ } else {
+ weekView = new WeekView(mContext);
+ AbsListView.LayoutParams params =
+ new AbsListView.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
+ FrameLayout.LayoutParams.WRAP_CONTENT);
+ weekView.setLayoutParams(params);
+ weekView.setClickable(true);
+ weekView.setOnTouchListener(this);
+ }
+
+ int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
+ Calendar.DAY_OF_WEEK) : -1;
+ weekView.init(position, selectedWeekDay, mFocusedMonth);
+
+ return weekView;
+ }
+
+ /**
+ * Changes which month is in focus and updates the view.
+ *
+ * @param month The month to show as in focus [0-11]
+ */
+ public void setFocusMonth(int month) {
+ if (mFocusedMonth == month) {
+ return;
+ }
+ mFocusedMonth = month;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) {
+ WeekView weekView = (WeekView) v;
+ // if we cannot find a day for the given location we are done
+ if (!weekView.getDayFromLocation(event.getX(), mTempDate)) {
+ return true;
+ }
+ // it is possible that the touched day is outside the valid range
+ // we draw whole weeks but range end can fall not on the week end
+ if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
+ return true;
+ }
+ onDateTapped(mTempDate);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Maintains the same hour/min/sec but moves the day to the tapped day.
+ *
+ * @param day The day that was tapped
+ */
+ private void onDateTapped(Calendar day) {
+ setSelectedDay(day);
+ setMonthDisplayed(day);
+ }
+
+ /**
+ * This is here so we can identify single tap events and set the
+ * selected day correctly
+ */
+ class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return true;
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * This is a dynamic view for drawing a single week. It can be configured to
+ * display the week number, start the week on a given day, or show a reduced
+ * number of days. It is intended for use as a single view within a
+ * ListView. See {@link WeeksAdapter} for usage.
+ * </p>
+ */
+ private class WeekView extends View {
+
+ private final Rect mTempRect = new Rect();
+
+ private final Paint mDrawPaint = new Paint();
+
+ private final Paint mMonthNumDrawPaint = new Paint();
+
+ // Cache the number strings so we don't have to recompute them each time
+ private String[] mDayNumbers;
+
+ // Quick lookup for checking which days are in the focus month
+ private boolean[] mFocusDay;
+
+ // Whether this view has a focused day.
+ private boolean mHasFocusedDay;
+
+ // Whether this view has only focused days.
+ private boolean mHasUnfocusedDay;
+
+ // The first day displayed by this item
+ private Calendar mFirstDay;
+
+ // The month of the first day in this week
+ private int mMonthOfFirstWeekDay = -1;
+
+ // The month of the last day in this week
+ private int mLastWeekDayMonth = -1;
+
+ // The position of this week, equivalent to weeks since the week of Jan
+ // 1st, 1900
+ private int mWeek = -1;
+
+ // Quick reference to the width of this view, matches parent
+ private int mWidth;
+
+ // The height this view should draw at in pixels, set by height param
+ private int mHeight;
+
+ // If this view contains the selected day
+ private boolean mHasSelectedDay = false;
+
+ // Which day is selected [0-6] or -1 if no day is selected
+ private int mSelectedDay = -1;
+
+ // The number of days + a spot for week number if it is displayed
+ private int mNumCells;
+
+ // The left edge of the selected day
+ private int mSelectedLeft = -1;
+
+ // The right edge of the selected day
+ private int mSelectedRight = -1;
+
+ public WeekView(Context context) {
+ super(context);
+
+ // Sets up any standard paints that will be used
+ initilaizePaints();
+ }
+
+ /**
+ * Initializes this week view.
+ *
+ * @param weekNumber The number of the week this view represents. The
+ * week number is a zero based index of the weeks since
+ * {@link android.widget.CalendarView#getMinDate()}.
+ * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no
+ * selected day.
+ * @param focusedMonth The month that is currently in focus i.e.
+ * highlighted.
+ */
+ public void init(int weekNumber, int selectedWeekDay, int focusedMonth) {
+ mSelectedDay = selectedWeekDay;
+ mHasSelectedDay = mSelectedDay != -1;
+ mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek;
+ mWeek = weekNumber;
+ mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
+
+ mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek);
+ mTempDate.setFirstDayOfWeek(mFirstDayOfWeek);
+
+ // Allocate space for caching the day numbers and focus values
+ mDayNumbers = new String[mNumCells];
+ mFocusDay = new boolean[mNumCells];
+
+ // If we're showing the week number calculate it based on Monday
+ int i = 0;
+ if (mShowWeekNumber) {
+ mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
+ mTempDate.get(Calendar.WEEK_OF_YEAR));
+ i++;
+ }
+
+ // Now adjust our starting day based on the start day of the week
+ int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK);
+ mTempDate.add(Calendar.DAY_OF_MONTH, diff);
+
+ mFirstDay = (Calendar) mTempDate.clone();
+ mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH);
+
+ mHasUnfocusedDay = true;
+ for (; i < mNumCells; i++) {
+ final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth);
+ mFocusDay[i] = isFocusedDay;
+ mHasFocusedDay |= isFocusedDay;
+ mHasUnfocusedDay &= !isFocusedDay;
+ // do not draw dates outside the valid range to avoid user confusion
+ if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
+ mDayNumbers[i] = "";
+ } else {
+ mDayNumbers[i] = String.format(Locale.getDefault(), "%d",
+ mTempDate.get(Calendar.DAY_OF_MONTH));
+ }
+ mTempDate.add(Calendar.DAY_OF_MONTH, 1);
+ }
+ // We do one extra add at the end of the loop, if that pushed us to
+ // new month undo it
+ if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) {
+ mTempDate.add(Calendar.DAY_OF_MONTH, -1);
+ }
+ mLastWeekDayMonth = mTempDate.get(Calendar.MONTH);
+
+ updateSelectionPositions();
+ }
+
+ /**
+ * Initialize the paint instances.
+ */
+ private void initilaizePaints() {
+ mDrawPaint.setFakeBoldText(false);
+ mDrawPaint.setAntiAlias(true);
+ mDrawPaint.setStyle(Paint.Style.FILL);
+
+ mMonthNumDrawPaint.setFakeBoldText(true);
+ mMonthNumDrawPaint.setAntiAlias(true);
+ mMonthNumDrawPaint.setStyle(Paint.Style.FILL);
+ mMonthNumDrawPaint.setTextAlign(Paint.Align.CENTER);
+ mMonthNumDrawPaint.setTextSize(mDateTextSize);
+ }
+
+ /**
+ * Returns the month of the first day in this week.
+ *
+ * @return The month the first day of this view is in.
+ */
+ public int getMonthOfFirstWeekDay() {
+ return mMonthOfFirstWeekDay;
+ }
+
+ /**
+ * Returns the month of the last day in this week
+ *
+ * @return The month the last day of this view is in
+ */
+ public int getMonthOfLastWeekDay() {
+ return mLastWeekDayMonth;
+ }
+
+ /**
+ * Returns the first day in this view.
+ *
+ * @return The first day in the view.
+ */
+ public Calendar getFirstDay() {
+ return mFirstDay;
+ }
+
+ /**
+ * Calculates the day that the given x position is in, accounting for
+ * week number.
+ *
+ * @param x The x position of the touch event.
+ * @return True if a day was found for the given location.
+ */
+ public boolean getDayFromLocation(float x, Calendar outCalendar) {
+ final boolean isLayoutRtl = isLayoutRtl();
+
+ int start;
+ int end;
+
+ if (isLayoutRtl) {
+ start = 0;
+ end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
+ } else {
+ start = mShowWeekNumber ? mWidth / mNumCells : 0;
+ end = mWidth;
+ }
+
+ if (x < start || x > end) {
+ outCalendar.clear();
+ return false;
+ }
+
+ // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
+ int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
+
+ if (isLayoutRtl) {
+ dayPosition = mDaysPerWeek - 1 - dayPosition;
+ }
+
+ outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
+ outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
+
+ return true;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ drawBackground(canvas);
+ drawWeekNumbersAndDates(canvas);
+ drawWeekSeparators(canvas);
+ drawSelectedDateVerticalBars(canvas);
+ }
+
+ /**
+ * This draws the selection highlight if a day is selected in this week.
+ *
+ * @param canvas The canvas to draw on
+ */
+ private void drawBackground(Canvas canvas) {
+ if (!mHasSelectedDay) {
+ return;
+ }
+ mDrawPaint.setColor(mSelectedWeekBackgroundColor);
+
+ mTempRect.top = mWeekSeperatorLineWidth;
+ mTempRect.bottom = mHeight;
+
+ final boolean isLayoutRtl = isLayoutRtl();
+
+ if (isLayoutRtl) {
+ mTempRect.left = 0;
+ mTempRect.right = mSelectedLeft - 2;
+ } else {
+ mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
+ mTempRect.right = mSelectedLeft - 2;
+ }
+ canvas.drawRect(mTempRect, mDrawPaint);
+
+ if (isLayoutRtl) {
+ mTempRect.left = mSelectedRight + 3;
+ mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
+ } else {
+ mTempRect.left = mSelectedRight + 3;
+ mTempRect.right = mWidth;
+ }
+ canvas.drawRect(mTempRect, mDrawPaint);
+ }
+
+ /**
+ * Draws the week and month day numbers for this week.
+ *
+ * @param canvas The canvas to draw on
+ */
+ private void drawWeekNumbersAndDates(Canvas canvas) {
+ final float textHeight = mDrawPaint.getTextSize();
+ final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
+ final int nDays = mNumCells;
+ final int divisor = 2 * nDays;
+
+ mDrawPaint.setTextAlign(Paint.Align.CENTER);
+ mDrawPaint.setTextSize(mDateTextSize);
+
+ int i = 0;
+
+ if (isLayoutRtl()) {
+ for (; i < nDays - 1; i++) {
+ mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
+ : mUnfocusedMonthDateColor);
+ int x = (2 * i + 1) * mWidth / divisor;
+ canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint);
+ }
+ if (mShowWeekNumber) {
+ mDrawPaint.setColor(mWeekNumberColor);
+ int x = mWidth - mWidth / divisor;
+ canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
+ }
+ } else {
+ if (mShowWeekNumber) {
+ mDrawPaint.setColor(mWeekNumberColor);
+ int x = mWidth / divisor;
+ canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
+ i++;
+ }
+ for (; i < nDays; i++) {
+ mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
+ : mUnfocusedMonthDateColor);
+ int x = (2 * i + 1) * mWidth / divisor;
+ canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
+ }
+ }
+ }
+
+ /**
+ * Draws a horizontal line for separating the weeks.
+ *
+ * @param canvas The canvas to draw on.
+ */
+ private void drawWeekSeparators(Canvas canvas) {
+ // If it is the topmost fully visible child do not draw separator line
+ int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
+ if (mListView.getChildAt(0).getTop() < 0) {
+ firstFullyVisiblePosition++;
+ }
+ if (firstFullyVisiblePosition == mWeek) {
+ return;
+ }
+ mDrawPaint.setColor(mWeekSeparatorLineColor);
+ mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth);
+ float startX;
+ float stopX;
+ if (isLayoutRtl()) {
+ startX = 0;
+ stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
+ } else {
+ startX = mShowWeekNumber ? mWidth / mNumCells : 0;
+ stopX = mWidth;
+ }
+ canvas.drawLine(startX, 0, stopX, 0, mDrawPaint);
+ }
+
+ /**
+ * Draws the selected date bars if this week has a selected day.
+ *
+ * @param canvas The canvas to draw on
+ */
+ private void drawSelectedDateVerticalBars(Canvas canvas) {
+ if (!mHasSelectedDay) {
+ return;
+ }
+ mSelectedDateVerticalBar.setBounds(
+ mSelectedLeft - mSelectedDateVerticalBarWidth / 2,
+ mWeekSeperatorLineWidth,
+ mSelectedLeft + mSelectedDateVerticalBarWidth / 2,
+ mHeight);
+ mSelectedDateVerticalBar.draw(canvas);
+ mSelectedDateVerticalBar.setBounds(
+ mSelectedRight - mSelectedDateVerticalBarWidth / 2,
+ mWeekSeperatorLineWidth,
+ mSelectedRight + mSelectedDateVerticalBarWidth / 2,
+ mHeight);
+ mSelectedDateVerticalBar.draw(canvas);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWidth = w;
+ updateSelectionPositions();
+ }
+
+ /**
+ * This calculates the positions for the selected day lines.
+ */
+ private void updateSelectionPositions() {
+ if (mHasSelectedDay) {
+ final boolean isLayoutRtl = isLayoutRtl();
+ int selectedPosition = mSelectedDay - mFirstDayOfWeek;
+ if (selectedPosition < 0) {
+ selectedPosition += 7;
+ }
+ if (mShowWeekNumber && !isLayoutRtl) {
+ selectedPosition++;
+ }
+ if (isLayoutRtl) {
+ mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
+
+ } else {
+ mSelectedLeft = selectedPosition * mWidth / mNumCells;
+ }
+ mSelectedRight = mSelectedLeft + mWidth / mNumCells;
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView
+ .getPaddingBottom()) / mShownWeekCount;
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
+ }
+ }
+
+}
diff --git a/core/java/android/widget/CalendarViewMaterialDelegate.java b/core/java/android/widget/CalendarViewMaterialDelegate.java
new file mode 100644
index 0000000..b0f3740
--- /dev/null
+++ b/core/java/android/widget/CalendarViewMaterialDelegate.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.MathUtils;
+
+import java.util.Calendar;
+import java.util.Locale;
+
+import libcore.icu.LocaleData;
+
+class CalendarViewMaterialDelegate extends CalendarView.AbstractCalendarViewDelegate {
+ private final DayPickerView mDayPickerView;
+
+ private CalendarView.OnDateChangeListener mOnDateChangeListener;
+
+ public CalendarViewMaterialDelegate(CalendarView delegator, Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(delegator, context);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.CalendarView, defStyleAttr, defStyleRes);
+ final int firstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek,
+ LocaleData.get(Locale.getDefault()).firstDayOfWeek);
+
+ final long minDate = parseDateToMillis(a.getString(
+ R.styleable.CalendarView_minDate), DEFAULT_MIN_DATE);
+ final long maxDate = parseDateToMillis(a.getString(
+ R.styleable.CalendarView_maxDate), DEFAULT_MAX_DATE);
+ if (maxDate < minDate) {
+ throw new IllegalArgumentException("max date cannot be before min date");
+ }
+
+ final long setDate = MathUtils.constrain(System.currentTimeMillis(), minDate, maxDate);
+ final int dateTextAppearanceResId = a.getResourceId(
+ R.styleable.CalendarView_dateTextAppearance,
+ R.style.TextAppearance_DeviceDefault_Small);
+
+ a.recycle();
+
+ mDayPickerView = new DayPickerView(context);
+ mDayPickerView.setFirstDayOfWeek(firstDayOfWeek);
+ mDayPickerView.setCalendarTextAppearance(dateTextAppearanceResId);
+ mDayPickerView.setMinDate(minDate);
+ mDayPickerView.setMaxDate(maxDate);
+ mDayPickerView.setDate(setDate, false, true);
+ mDayPickerView.setOnDaySelectedListener(mOnDaySelectedListener);
+
+ delegator.addView(mDayPickerView);
+ }
+
+ private long parseDateToMillis(String dateStr, String defaultDateStr) {
+ final Calendar tempCalendar = Calendar.getInstance();
+ if (TextUtils.isEmpty(dateStr) || !parseDate(dateStr, tempCalendar)) {
+ parseDate(defaultDateStr, tempCalendar);
+ }
+ return tempCalendar.getTimeInMillis();
+ }
+
+ @Override
+ public void setShownWeekCount(int count) {
+ // Deprecated.
+ }
+
+ @Override
+ public int getShownWeekCount() {
+ // Deprecated.
+ return 0;
+ }
+
+ @Override
+ public void setSelectedWeekBackgroundColor(int color) {
+ // TODO: Should use a ColorStateList. Deprecate?
+ }
+
+ @Override
+ public int getSelectedWeekBackgroundColor() {
+ return 0;
+ }
+
+ @Override
+ public void setFocusedMonthDateColor(int color) {
+ // TODO: Should use a ColorStateList. Deprecate?
+ }
+
+ @Override
+ public int getFocusedMonthDateColor() {
+ return 0;
+ }
+
+ @Override
+ public void setUnfocusedMonthDateColor(int color) {
+ // TODO: Should use a ColorStateList. Deprecate?
+ }
+
+ @Override
+ public int getUnfocusedMonthDateColor() {
+ return 0;
+ }
+
+ @Override
+ public void setWeekDayTextAppearance(int resourceId) {
+
+ }
+
+ @Override
+ public int getWeekDayTextAppearance() {
+ return 0;
+ }
+
+ @Override
+ public void setDateTextAppearance(int resourceId) {
+
+ }
+
+ @Override
+ public int getDateTextAppearance() {
+ return 0;
+ }
+
+ @Override
+ public void setWeekNumberColor(int color) {
+ // Deprecated.
+ }
+
+ @Override
+ public int getWeekNumberColor() {
+ // Deprecated.
+ return 0;
+ }
+
+ @Override
+ public void setWeekSeparatorLineColor(int color) {
+ // Deprecated.
+ }
+
+ @Override
+ public int getWeekSeparatorLineColor() {
+ // Deprecated.
+ return 0;
+ }
+
+ @Override
+ public void setSelectedDateVerticalBar(int resourceId) {
+ // Deprecated.
+ }
+
+ @Override
+ public void setSelectedDateVerticalBar(Drawable drawable) {
+ // Deprecated.
+ }
+
+ @Override
+ public Drawable getSelectedDateVerticalBar() {
+ // Deprecated.
+ return null;
+ }
+
+ @Override
+ public void setMinDate(long minDate) {
+ mDayPickerView.setMinDate(minDate);
+ }
+
+ @Override
+ public long getMinDate() {
+ return mDayPickerView.getMinDate();
+ }
+
+ @Override
+ public void setMaxDate(long maxDate) {
+ mDayPickerView.setMaxDate(maxDate);
+ }
+
+ @Override
+ public long getMaxDate() {
+ return mDayPickerView.getMaxDate();
+ }
+
+ @Override
+ public void setShowWeekNumber(boolean showWeekNumber) {
+ // Deprecated.
+ }
+
+ @Override
+ public boolean getShowWeekNumber() {
+ // Deprecated.
+ return false;
+ }
+
+ @Override
+ public void setFirstDayOfWeek(int firstDayOfWeek) {
+ mDayPickerView.setFirstDayOfWeek(firstDayOfWeek);
+ }
+
+ @Override
+ public int getFirstDayOfWeek() {
+ return mDayPickerView.getFirstDayOfWeek();
+ }
+
+ @Override
+ public void setDate(long date) {
+ mDayPickerView.setDate(date, true, false);
+ }
+
+ @Override
+ public void setDate(long date, boolean animate, boolean center) {
+ mDayPickerView.setDate(date, animate, center);
+ }
+
+ @Override
+ public long getDate() {
+ return mDayPickerView.getDate();
+ }
+
+ @Override
+ public void setOnDateChangeListener(CalendarView.OnDateChangeListener listener) {
+ mOnDateChangeListener = listener;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ // Nothing to do here, configuration changes are already propagated
+ // by ViewGroup.
+ }
+
+ private final DayPickerView.OnDaySelectedListener mOnDaySelectedListener =
+ new DayPickerView.OnDaySelectedListener() {
+ @Override
+ public void onDaySelected(DayPickerView view, Calendar day) {
+ if (mOnDateChangeListener != null) {
+ final int year = day.get(Calendar.YEAR);
+ final int month = day.get(Calendar.MONTH);
+ final int dayOfMonth = day.get(Calendar.DAY_OF_MONTH);
+ mOnDateChangeListener.onSelectedDayChange(mDelegator, year, month, dayOfMonth);
+ }
+ }
+ };
+}
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index eb8e8aa..69969a9 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -267,6 +267,12 @@ public class CheckedTextView extends TextView implements Checkable {
if (mHasCheckMarkTintMode) {
mCheckMarkDrawable.setTintMode(mCheckMarkTintMode);
}
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mCheckMarkDrawable.isStateful()) {
+ mCheckMarkDrawable.setState(getDrawableState());
+ }
}
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 7d9d305..447ccc2 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -29,6 +29,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.Gravity;
+import android.view.SoundEffectConstants;
import android.view.ViewDebug;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -114,15 +115,16 @@ public abstract class CompoundButton extends Button implements Checkable {
@Override
public boolean performClick() {
- /*
- * XXX: These are tiny, need some surrounding 'expanded touch area',
- * which will need to be implemented in Button if we only override
- * performClick()
- */
-
- /* When clicked, toggle the state */
toggle();
- return super.performClick();
+
+ final boolean handled = super.performClick();
+ if (!handled) {
+ // View only makes a sound effect if the onClickListener was
+ // called, so we'll need to make one here instead.
+ playSoundEffect(SoundEffectConstants.CLICK);
+ }
+
+ return handled;
}
@ViewDebug.ExportedProperty
@@ -313,6 +315,12 @@ public abstract class CompoundButton extends Button implements Checkable {
if (mHasButtonTintMode) {
mButtonDrawable.setTintMode(mButtonTintMode);
}
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mButtonDrawable.isStateful()) {
+ mButtonDrawable.setState(getDrawableState());
+ }
}
}
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 3e4eb02..0ca08e1 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -51,12 +51,19 @@ import java.util.TimeZone;
import libcore.icu.ICU;
/**
- * This class is a widget for selecting a date. The date can be selected by a
- * year, month, and day spinners or a {@link CalendarView}. The set of spinners
- * and the calendar view are automatically synchronized. The client can
- * customize whether only the spinners, or only the calendar view, or both to be
- * displayed. Also the minimal and maximal date from which dates to be selected
- * can be customized.
+ * Provides a widget for selecting a date.
+ * <p>
+ * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is
+ * set to {@code spinner}, the date can be selected using year, month, and day
+ * spinners or a {@link CalendarView}. The set of spinners and the calendar
+ * view are automatically synchronized. The client can customize whether only
+ * the spinners, or only the calendar view, or both to be displayed.
+ * </p>
+ * <p>
+ * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is
+ * set to {@code calendar}, the month and day can be selected using a
+ * calendar-style view while the year can be selected separately using a list.
+ * </p>
* <p>
* See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
* guide.
@@ -80,6 +87,7 @@ import libcore.icu.ICU;
* @attr ref android.R.styleable#DatePicker_yearListItemTextAppearance
* @attr ref android.R.styleable#DatePicker_yearListSelectorColor
* @attr ref android.R.styleable#DatePicker_calendarTextColor
+ * @attr ref android.R.styleable#DatePicker_datePickerMode
*/
@Widget
public class DatePicker extends FrameLayout {
@@ -357,6 +365,10 @@ public class DatePicker extends FrameLayout {
/**
* Gets the {@link CalendarView}.
+ * <p>
+ * This method returns {@code null} when the
+ * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set
+ * to {@code calendar}.
*
* @return The calendar view.
* @see #getCalendarViewShown()
@@ -367,6 +379,10 @@ public class DatePicker extends FrameLayout {
/**
* Sets whether the {@link CalendarView} is shown.
+ * <p>
+ * Calling this method has no effect when the
+ * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set
+ * to {@code calendar}.
*
* @param shown True if the calendar view is to be shown.
*/
diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
index 69a2b06..fc75346 100755
--- a/core/java/android/widget/DatePickerCalendarDelegate.java
+++ b/core/java/android/widget/DatePickerCalendarDelegate.java
@@ -21,13 +21,11 @@ import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.AttributeSet;
-import android.util.SparseArray;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.View;
@@ -185,7 +183,13 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
mHeaderYearTextView.getTextColors(), R.attr.state_selected,
headerSelectedTextColor));
- mDayPickerView = new DayPickerView(mContext, this);
+ mDayPickerView = new DayPickerView(mContext);
+ mDayPickerView.setFirstDayOfWeek(mFirstDayOfWeek);
+ mDayPickerView.setMinDate(mMinDate.getTimeInMillis());
+ mDayPickerView.setMaxDate(mMaxDate.getTimeInMillis());
+ mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
+ mDayPickerView.setOnDaySelectedListener(mOnDaySelectedListener);
+
mYearPickerView = new YearPickerView(mContext);
mYearPickerView.init(this);
@@ -325,7 +329,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
String fullDateText = DateUtils.formatDateTime(mContext, millis, flags);
mAnimator.announceForAccessibility(fullDateText);
}
- updatePickers();
}
private void setCurrentView(final int viewIndex) {
@@ -333,7 +336,7 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
switch (viewIndex) {
case MONTH_AND_DAY_VIEW:
- mDayPickerView.onDateChanged();
+ mDayPickerView.setDate(getSelectedDay().getTimeInMillis());
if (mCurrentView != viewIndex) {
mMonthAndDayLayout.setSelected(true);
mHeaderYearTextView.setSelected(false);
@@ -365,11 +368,13 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
@Override
public void init(int year, int monthOfYear, int dayOfMonth,
DatePicker.OnDateChangedListener callBack) {
- mDateChangedListener = callBack;
mCurrentDate.set(Calendar.YEAR, year);
mCurrentDate.set(Calendar.MONTH, monthOfYear);
mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
- updateDisplay(false);
+
+ mDateChangedListener = callBack;
+
+ onDateChanged(false, false);
}
@Override
@@ -377,10 +382,29 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
mCurrentDate.set(Calendar.YEAR, year);
mCurrentDate.set(Calendar.MONTH, month);
mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
- if (mDateChangedListener != null) {
- mDateChangedListener.onDateChanged(mDelegator, year, month, dayOfMonth);
+
+ onDateChanged(false, true);
+ }
+
+ private void onDateChanged(boolean fromUser, boolean callbackToClient) {
+ if (callbackToClient && mDateChangedListener != null) {
+ final int year = mCurrentDate.get(Calendar.YEAR);
+ final int monthOfYear = mCurrentDate.get(Calendar.MONTH);
+ final int dayOfMonth = mCurrentDate.get(Calendar.DAY_OF_MONTH);
+ mDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
+ }
+
+ for (OnDateChangedListener listener : mListeners) {
+ listener.onDateChanged();
+ }
+
+ mDayPickerView.setDate(getSelectedDay().getTimeInMillis());
+
+ updateDisplay(fromUser);
+
+ if (fromUser) {
+ tryVibrate();
}
- updateDisplay(false);
}
@Override
@@ -407,11 +431,11 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
}
if (mCurrentDate.before(mTempDate)) {
mCurrentDate.setTimeInMillis(minDate);
- updatePickers();
- updateDisplay(false);
+ onDateChanged(false, true);
}
mMinDate.setTimeInMillis(minDate);
- mDayPickerView.goTo(getSelectedDay(), false, true, true);
+ mDayPickerView.setMinDate(minDate);
+ mYearPickerView.setRange(mMinDate, mMaxDate);
}
@Override
@@ -428,11 +452,11 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
}
if (mCurrentDate.after(mTempDate)) {
mCurrentDate.setTimeInMillis(maxDate);
- updatePickers();
- updateDisplay(false);
+ onDateChanged(false, true);
}
mMaxDate.setTimeInMillis(maxDate);
- mDayPickerView.goTo(getSelectedDay(), false, true, true);
+ mDayPickerView.setMaxDate(maxDate);
+ mYearPickerView.setRange(mMinDate, mMaxDate);
}
@Override
@@ -443,6 +467,8 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
@Override
public void setFirstDayOfWeek(int firstDayOfWeek) {
mFirstDayOfWeek = firstDayOfWeek;
+
+ mDayPickerView.setFirstDayOfWeek(firstDayOfWeek);
}
@Override
@@ -454,36 +480,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
}
@Override
- public int getMinYear() {
- return mMinDate.get(Calendar.YEAR);
- }
-
- @Override
- public int getMaxYear() {
- return mMaxDate.get(Calendar.YEAR);
- }
-
- @Override
- public int getMinMonth() {
- return mMinDate.get(Calendar.MONTH);
- }
-
- @Override
- public int getMaxMonth() {
- return mMaxDate.get(Calendar.MONTH);
- }
-
- @Override
- public int getMinDay() {
- return mMinDate.get(Calendar.DAY_OF_MONTH);
- }
-
- @Override
- public int getMaxDay() {
- return mMaxDate.get(Calendar.DAY_OF_MONTH);
- }
-
- @Override
public void setEnabled(boolean enabled) {
mMonthAndDayLayout.setEnabled(enabled);
mHeaderYearTextView.setEnabled(enabled);
@@ -595,9 +591,10 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
public void onYearSelected(int year) {
adjustDayInMonthIfNeeded(mCurrentDate.get(Calendar.MONTH), year);
mCurrentDate.set(Calendar.YEAR, year);
- updatePickers();
+ onDateChanged(true, true);
+
+ // Auto-advance to month and day view.
setCurrentView(MONTH_AND_DAY_VIEW);
- updateDisplay(true);
}
// If the newly selected month / year does not contain the currently selected day number,
@@ -635,31 +632,11 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
}
@Override
- public void onDayOfMonthSelected(int year, int month, int day) {
- mCurrentDate.set(Calendar.YEAR, year);
- mCurrentDate.set(Calendar.MONTH, month);
- mCurrentDate.set(Calendar.DAY_OF_MONTH, day);
- updatePickers();
- updateDisplay(true);
- }
-
- private void updatePickers() {
- for (OnDateChangedListener listener : mListeners) {
- listener.onDateChanged();
- }
- }
-
- @Override
public void registerOnDateChangedListener(OnDateChangedListener listener) {
mListeners.add(listener);
}
@Override
- public void unregisterOnDateChangedListener(OnDateChangedListener listener) {
- mListeners.remove(listener);
- }
-
- @Override
public Calendar getSelectedDay() {
return mCurrentDate;
}
@@ -680,6 +657,18 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
}
/**
+ * Listener called when the user selects a day in the day picker view.
+ */
+ private final DayPickerView.OnDaySelectedListener
+ mOnDaySelectedListener = new DayPickerView.OnDaySelectedListener() {
+ @Override
+ public void onDaySelected(DayPickerView view, Calendar day) {
+ mCurrentDate.setTimeInMillis(day.getTimeInMillis());
+ onDateChanged(true, true);
+ }
+ };
+
+ /**
* Class for managing state storing/restoring.
*/
private static class SavedState extends View.BaseSavedState {
diff --git a/core/java/android/widget/DatePickerController.java b/core/java/android/widget/DatePickerController.java
index 059709d..8f809ba 100644
--- a/core/java/android/widget/DatePickerController.java
+++ b/core/java/android/widget/DatePickerController.java
@@ -27,31 +27,9 @@ interface DatePickerController {
void onYearSelected(int year);
- void onDayOfMonthSelected(int year, int month, int day);
-
void registerOnDateChangedListener(OnDateChangedListener listener);
- void unregisterOnDateChangedListener(OnDateChangedListener listener);
-
Calendar getSelectedDay();
- void setFirstDayOfWeek(int firstDayOfWeek);
- int getFirstDayOfWeek();
-
- int getMinYear();
- int getMaxYear();
-
- int getMinMonth();
- int getMaxMonth();
-
- int getMinDay();
- int getMaxDay();
-
- void setMinDate(long minDate);
- Calendar getMinDate();
-
- void setMaxDate(long maxDate);
- Calendar getMaxDate();
-
void tryVibrate();
}
diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java
index 86cd52f..db17df7 100644
--- a/core/java/android/widget/DateTimeView.java
+++ b/core/java/android/widget/DateTimeView.java
@@ -32,6 +32,7 @@ import android.widget.RemoteViews.RemoteView;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
//
@@ -62,8 +63,8 @@ public class DateTimeView extends TextView {
int mLastDisplay = -1;
DateFormat mLastFormat;
- private boolean mAttachedToWindow;
private long mUpdateTimeMillis;
+ private static final ThreadLocal<ReceiverInfo> sReceiverInfo = new ThreadLocal<ReceiverInfo>();
public DateTimeView(Context context) {
super(context);
@@ -76,15 +77,21 @@ public class DateTimeView extends TextView {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- registerReceivers();
- mAttachedToWindow = true;
+ ReceiverInfo ri = sReceiverInfo.get();
+ if (ri == null) {
+ ri = new ReceiverInfo();
+ sReceiverInfo.set(ri);
+ }
+ ri.addView(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- unregisterReceivers();
- mAttachedToWindow = false;
+ final ReceiverInfo ri = sReceiverInfo.get();
+ if (ri != null) {
+ ri.removeView(this);
+ }
}
@android.view.RemotableViewMethod
@@ -189,37 +196,82 @@ public class DateTimeView extends TextView {
return android.text.format.DateFormat.getTimeFormat(getContext());
}
- private void registerReceivers() {
- Context context = getContext();
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_TIME_TICK);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- context.registerReceiver(mBroadcastReceiver, filter);
+ void clearFormatAndUpdate() {
+ mLastFormat = null;
+ update();
}
- private void unregisterReceivers() {
- Context context = getContext();
- context.unregisterReceiver(mBroadcastReceiver);
- }
+ private static class ReceiverInfo {
+ private final ArrayList<DateTimeView> mAttachedViews = new ArrayList<DateTimeView>();
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_TIME_TICK.equals(action)) {
+ if (System.currentTimeMillis() < getSoonestUpdateTime()) {
+ // The update() function takes a few milliseconds to run because of
+ // all of the time conversions it needs to do, so we can't do that
+ // every minute.
+ return;
+ }
+ }
+ // ACTION_TIME_CHANGED can also signal a change of 12/24 hr. format.
+ updateAll();
+ }
+ };
+
+ private final ContentObserver mObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateAll();
+ }
+ };
+
+ public void addView(DateTimeView v) {
+ final boolean register = mAttachedViews.isEmpty();
+ mAttachedViews.add(v);
+ if (register) {
+ register(v.getContext().getApplicationContext());
+ }
+ }
- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_TIME_TICK.equals(action)) {
- if (System.currentTimeMillis() < mUpdateTimeMillis) {
- // The update() function takes a few milliseconds to run because of
- // all of the time conversions it needs to do, so we can't do that
- // every minute.
- return;
+ public void removeView(DateTimeView v) {
+ mAttachedViews.remove(v);
+ if (mAttachedViews.isEmpty()) {
+ unregister(v.getContext().getApplicationContext());
+ }
+ }
+
+ void updateAll() {
+ final int count = mAttachedViews.size();
+ for (int i = 0; i < count; i++) {
+ mAttachedViews.get(i).clearFormatAndUpdate();
+ }
+ }
+
+ long getSoonestUpdateTime() {
+ long result = Long.MAX_VALUE;
+ final int count = mAttachedViews.size();
+ for (int i = 0; i < count; i++) {
+ final long time = mAttachedViews.get(i).mUpdateTimeMillis;
+ if (time < result) {
+ result = time;
}
}
- // ACTION_TIME_CHANGED can also signal a change of 12/24 hr. format.
- mLastFormat = null;
- update();
+ return result;
}
- };
+
+ void register(Context context) {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_TIME_TICK);
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ context.registerReceiver(mReceiver, filter);
+ }
+
+ void unregister(Context context) {
+ context.unregisterReceiver(mReceiver);
+ }
+ }
}
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index ca4095e..7db3fb9 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -16,15 +16,12 @@
package android.widget;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
-import android.os.Build;
import android.os.Bundle;
-import android.os.Handler;
-import android.util.AttributeSet;
import android.util.Log;
+import android.util.MathUtils;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
@@ -37,9 +34,7 @@ import java.util.Locale;
/**
* This displays a list of months in a calendar format with selectable days.
*/
-class DayPickerView extends ListView implements AbsListView.OnScrollListener,
- OnDateChangedListener {
-
+class DayPickerView extends ListView implements AbsListView.OnScrollListener {
private static final String TAG = "DayPickerView";
// How long the GoTo fling animation should last
@@ -48,18 +43,24 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener,
// How long to wait after receiving an onScrollStateChanged notification before acting on it
private static final int SCROLL_CHANGE_DELAY = 40;
- private static int LIST_TOP_OFFSET = -1; // so that the top line will be under the separator
+ // so that the top line will be under the separator
+ private static final int LIST_TOP_OFFSET = -1;
- private SimpleDateFormat mYearFormat = new SimpleDateFormat("yyyy", Locale.getDefault());
+ private final SimpleMonthAdapter mAdapter = new SimpleMonthAdapter(getContext());
- // These affect the scroll speed and feel
- private float mFriction = 1.0f;
+ private final ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(this);
+
+ private SimpleDateFormat mYearFormat = new SimpleDateFormat("yyyy", Locale.getDefault());
// highlighted time
private Calendar mSelectedDay = Calendar.getInstance();
- private SimpleMonthAdapter mAdapter;
-
private Calendar mTempDay = Calendar.getInstance();
+ private Calendar mMinDate = Calendar.getInstance();
+ private Calendar mMaxDate = Calendar.getInstance();
+
+ private Calendar mTempCalendar;
+
+ private OnDaySelectedListener mOnDaySelectedListener;
// which month should be displayed/highlighted [0-11]
private int mCurrentMonthDisplayed;
@@ -68,60 +69,91 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener,
// used for tracking what state listview is in
private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
- private DatePickerController mController;
private boolean mPerformingScroll;
- private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(this);
-
- public DayPickerView(Context context, DatePickerController controller) {
+ public DayPickerView(Context context) {
super(context);
- init();
- setController(controller);
- }
- public void setController(DatePickerController controller) {
- if (mController != null) {
- mController.unregisterOnDateChangedListener(this);
- }
- mController = controller;
- mController.registerOnDateChangedListener(this);
- setUpAdapter();
setAdapter(mAdapter);
- onDateChanged();
- }
-
- public void init() {
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
setDrawSelectorOnTop(false);
-
setUpListView();
+
+ goTo(mSelectedDay.getTimeInMillis(), false, false, true);
+
+ mAdapter.setOnDaySelectedListener(mProxyOnDaySelectedListener);
}
- public void onChange() {
- setUpAdapter();
- setAdapter(mAdapter);
+ /**
+ * Sets the currently selected date to the specified timestamp. Jumps
+ * immediately to the new date. To animate to the new date, use
+ * {@link #setDate(long, boolean, boolean)}.
+ *
+ * @param timeInMillis
+ */
+ public void setDate(long timeInMillis) {
+ setDate(timeInMillis, false, true);
+ }
+
+ public void setDate(long timeInMillis, boolean animate, boolean forceScroll) {
+ goTo(timeInMillis, animate, true, forceScroll);
+ }
+
+ public long getDate() {
+ return mSelectedDay.getTimeInMillis();
+ }
+
+ public void setFirstDayOfWeek(int firstDayOfWeek) {
+ mAdapter.setFirstDayOfWeek(firstDayOfWeek);
+ }
+
+ public int getFirstDayOfWeek() {
+ return mAdapter.getFirstDayOfWeek();
+ }
+
+ public void setMinDate(long timeInMillis) {
+ mMinDate.setTimeInMillis(timeInMillis);
+ onRangeChanged();
+ }
+
+ public long getMinDate() {
+ return mMinDate.getTimeInMillis();
+ }
+
+ public void setMaxDate(long timeInMillis) {
+ mMaxDate.setTimeInMillis(timeInMillis);
+ onRangeChanged();
+ }
+
+ public long getMaxDate() {
+ return mMaxDate.getTimeInMillis();
}
/**
- * Creates a new adapter if necessary and sets up its parameters. Override
- * this method to provide a custom adapter.
+ * Handles changes to date range.
*/
- protected void setUpAdapter() {
- if (mAdapter == null) {
- mAdapter = new SimpleMonthAdapter(getContext(), mController);
- } else {
- mAdapter.setSelectedDay(mSelectedDay);
- mAdapter.notifyDataSetChanged();
- }
- // refresh the view with the new parameters
- mAdapter.notifyDataSetChanged();
+ public void onRangeChanged() {
+ mAdapter.setRange(mMinDate, mMaxDate);
+
+ // Changing the min/max date changes the selection position since we
+ // don't really have stable IDs. Jumps immediately to the new position.
+ goTo(mSelectedDay.getTimeInMillis(), false, false, true);
+ }
+
+ /**
+ * Sets the listener to call when the user selects a day.
+ *
+ * @param listener The listener to call.
+ */
+ public void setOnDaySelectedListener(OnDaySelectedListener listener) {
+ mOnDaySelectedListener = listener;
}
/*
* Sets all the required fields for the list view. Override this method to
* set a different list view behavior.
*/
- protected void setUpListView() {
+ private void setUpListView() {
// Transparent background on scroll
setCacheColorHint(0);
// No dividers
@@ -134,26 +166,27 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener,
setOnScrollListener(this);
setFadingEdgeLength(0);
// Make the scrolling behavior nicer
- setFriction(ViewConfiguration.getScrollFriction() * mFriction);
+ setFriction(ViewConfiguration.getScrollFriction());
}
- private int getDiffMonths(Calendar start, Calendar end){
+ private int getDiffMonths(Calendar start, Calendar end) {
final int diffYears = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);
final int diffMonths = end.get(Calendar.MONTH) - start.get(Calendar.MONTH) + 12 * diffYears;
return diffMonths;
}
- private int getPositionFromDay(Calendar day) {
- final int diffMonthMax = getDiffMonths(mController.getMinDate(), mController.getMaxDate());
- int diffMonth = getDiffMonths(mController.getMinDate(), day);
+ private int getPositionFromDay(long timeInMillis) {
+ final int diffMonthMax = getDiffMonths(mMinDate, mMaxDate);
+ final int diffMonth = getDiffMonths(mMinDate, getTempCalendarForTime(timeInMillis));
+ return MathUtils.constrain(diffMonth, 0, diffMonthMax);
+ }
- if (diffMonth < 0 ) {
- diffMonth = 0;
- } else if (diffMonth > diffMonthMax) {
- diffMonth = diffMonthMax;
+ private Calendar getTempCalendarForTime(long timeInMillis) {
+ if (mTempCalendar == null) {
+ mTempCalendar = Calendar.getInstance();
}
-
- return diffMonth;
+ mTempCalendar.setTimeInMillis(timeInMillis);
+ return mTempCalendar;
}
/**
@@ -171,15 +204,14 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener,
* visible
* @return Whether or not the view animated to the new location
*/
- public boolean goTo(Calendar day, boolean animate, boolean setSelected,
- boolean forceScroll) {
+ private boolean goTo(long day, boolean animate, boolean setSelected, boolean forceScroll) {
// Set the selected day
if (setSelected) {
- mSelectedDay.setTimeInMillis(day.getTimeInMillis());
+ mSelectedDay.setTimeInMillis(day);
}
- mTempDay.setTimeInMillis(day.getTimeInMillis());
+ mTempDay.setTimeInMillis(day);
final int position = getPositionFromDay(day);
View child;
@@ -273,6 +305,10 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener,
mAdapter.setCalendarTextColor(colors);
}
+ void setCalendarTextAppearance(int resId) {
+ mAdapter.setCalendarTextAppearance(resId);
+ }
+
protected class ScrollStateRunnable implements Runnable {
private int mNewState;
private View mParent;
@@ -361,11 +397,6 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener,
return firstPosition + mostVisibleIndex;
}
- @Override
- public void onDateChanged() {
- goTo(mController.getSelectedDay(), false, true, true);
- }
-
/**
* Attempts to return the date that has accessibility focus.
*
@@ -435,7 +466,7 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener,
}
private String getMonthAndYearString(Calendar day) {
- StringBuffer sbuf = new StringBuffer();
+ final StringBuilder sbuf = new StringBuilder();
sbuf.append(day.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault()));
sbuf.append(" ");
sbuf.append(mYearFormat.format(day.getTime()));
@@ -449,8 +480,8 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener,
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
- info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
- info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
}
/**
@@ -464,10 +495,10 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener,
}
// Figure out what month is showing.
- int firstVisiblePosition = getFirstVisiblePosition();
- int month = firstVisiblePosition % 12;
- int year = firstVisiblePosition / 12 + mController.getMinYear();
- Calendar day = Calendar.getInstance();
+ final int firstVisiblePosition = getFirstVisiblePosition();
+ final int month = firstVisiblePosition % 12;
+ final int year = firstVisiblePosition / 12 + mMinDate.get(Calendar.YEAR);
+ final Calendar day = Calendar.getInstance();
day.set(year, month, 1);
// Scroll either forward or backward one month.
@@ -494,8 +525,22 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener,
// Go to that month.
announceForAccessibility(getMonthAndYearString(day));
- goTo(day, true, false, true);
+ goTo(day.getTimeInMillis(), true, false, true);
mPerformingScroll = true;
return true;
}
+
+ public interface OnDaySelectedListener {
+ public void onDaySelected(DayPickerView view, Calendar day);
+ }
+
+ private final SimpleMonthAdapter.OnDaySelectedListener
+ mProxyOnDaySelectedListener = new SimpleMonthAdapter.OnDaySelectedListener() {
+ @Override
+ public void onDaySelected(SimpleMonthAdapter adapter, Calendar day) {
+ if (mOnDaySelectedListener != null) {
+ mOnDaySelectedListener.onDaySelected(DayPickerView.this, day);
+ }
+ }
+ };
}
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index 0687905..fe143de 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -106,6 +106,9 @@ class FastScroller {
*/
private final int[] mPreviewResId = new int[2];
+ /** The minimum touch target size in pixels. */
+ private final int mMinimumTouchTarget;
+
/**
* Padding in pixels around the preview text. Applied as layout margins to
* the preview text and padding to the preview image.
@@ -254,6 +257,9 @@ class FastScroller {
mPrimaryText = createPreviewTextView(context);
mSecondaryText = createPreviewTextView(context);
+ mMinimumTouchTarget = listView.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.fast_scroller_minimum_touch_target);
+
setStyle(styleResId);
final ViewGroupOverlay overlay = listView.getOverlay();
@@ -1194,17 +1200,37 @@ class FastScroller {
return MathUtils.constrain((y - offset) / range, 0f, 1f);
}
+ /**
+ * Calculates the thumb position based on the visible items.
+ *
+ * @param firstVisibleItem First visible item, >= 0.
+ * @param visibleItemCount Number of visible items, >= 0.
+ * @param totalItemCount Total number of items, >= 0.
+ * @return
+ */
private float getPosFromItemCount(
int firstVisibleItem, int visibleItemCount, int totalItemCount) {
- if (mSectionIndexer == null || mListAdapter == null) {
+ final SectionIndexer sectionIndexer = mSectionIndexer;
+ if (sectionIndexer == null || mListAdapter == null) {
getSectionsFromIndexer();
}
- final boolean hasSections = mSectionIndexer != null && mSections != null
+ if (visibleItemCount == 0 || totalItemCount == 0) {
+ // No items are visible.
+ return 0;
+ }
+
+ final boolean hasSections = sectionIndexer != null && mSections != null
&& mSections.length > 0;
if (!hasSections || !mMatchDragPosition) {
- return (float) firstVisibleItem / (totalItemCount - visibleItemCount);
+ if (visibleItemCount == totalItemCount) {
+ // All items are visible.
+ return 0;
+ } else {
+ return (float) firstVisibleItem / (totalItemCount - visibleItemCount);
+ }
}
+
// Ignore headers.
firstVisibleItem -= mHeaderCount;
if (firstVisibleItem < 0) {
@@ -1222,14 +1248,14 @@ class FastScroller {
}
// Number of rows in this section.
- final int section = mSectionIndexer.getSectionForPosition(firstVisibleItem);
- final int sectionPos = mSectionIndexer.getPositionForSection(section);
+ final int section = sectionIndexer.getSectionForPosition(firstVisibleItem);
+ final int sectionPos = sectionIndexer.getPositionForSection(section);
final int sectionCount = mSections.length;
final int positionsInSection;
if (section < sectionCount - 1) {
final int nextSectionPos;
if (section + 1 < sectionCount) {
- nextSectionPos = mSectionIndexer.getPositionForSection(section + 1);
+ nextSectionPos = sectionIndexer.getPositionForSection(section + 1);
} else {
nextSectionPos = totalItemCount - 1;
}
@@ -1454,10 +1480,18 @@ class FastScroller {
}
private boolean isPointInsideX(float x) {
+ final float offset = mThumbImage.getTranslationX();
+ final float left = mThumbImage.getLeft() + offset;
+ final float right = mThumbImage.getRight() + offset;
+
+ // Apply the minimum touch target size.
+ final float targetSizeDiff = mMinimumTouchTarget - (right - left);
+ final float adjust = targetSizeDiff > 0 ? targetSizeDiff : 0;
+
if (mLayoutFromRight) {
- return x >= mThumbImage.getLeft();
+ return x >= mThumbImage.getLeft() - adjust;
} else {
- return x <= mThumbImage.getRight();
+ return x <= mThumbImage.getRight() + adjust;
}
}
@@ -1465,7 +1499,12 @@ class FastScroller {
final float offset = mThumbImage.getTranslationY();
final float top = mThumbImage.getTop() + offset;
final float bottom = mThumbImage.getBottom() + offset;
- return y >= top && y <= bottom;
+
+ // Apply the minimum touch target size.
+ final float targetSizeDiff = mMinimumTouchTarget - (bottom - top);
+ final float adjust = targetSizeDiff > 0 ? targetSizeDiff / 2 : 0;
+
+ return y >= (top - adjust) && y <= (bottom + adjust);
}
/**
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index e317524..d974c29 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -384,6 +384,12 @@ public class FrameLayout extends ViewGroup {
if (mHasForegroundTintMode) {
mForeground.setTintMode(mForegroundTintMode);
}
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mForeground.isStateful()) {
+ mForeground.setState(getDrawableState());
+ }
}
}
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index defc26c..161ae7e 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -1613,7 +1613,11 @@ public class GridLayout extends ViewGroup {
equivalent to the single-source shortest paths problem on a digraph, for
which the O(n^2) Bellman-Ford algorithm the most commonly used general solution.
*/
- private void solve(Arc[] arcs, int[] locations) {
+ private boolean solve(Arc[] arcs, int[] locations) {
+ return solve(arcs, locations, true);
+ }
+
+ private boolean solve(Arc[] arcs, int[] locations, boolean modifyOnError) {
String axisName = horizontal ? "horizontal" : "vertical";
int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1.
boolean[] originalCulprits = null;
@@ -1631,10 +1635,14 @@ public class GridLayout extends ViewGroup {
if (originalCulprits != null) {
logError(axisName, arcs, originalCulprits);
}
- return;
+ return true;
}
}
+ if (!modifyOnError) {
+ return false; // cannot solve with these constraints
+ }
+
boolean[] culprits = new boolean[arcs.length];
for (int i = 0; i < N; i++) {
for (int j = 0, length = arcs.length; j < length; j++) {
@@ -1658,6 +1666,7 @@ public class GridLayout extends ViewGroup {
}
}
}
+ return true;
}
private void computeMargins(boolean leading) {
@@ -1697,8 +1706,8 @@ public class GridLayout extends ViewGroup {
return trailingMargins;
}
- private void solve(int[] a) {
- solve(getArcs(), a);
+ private boolean solve(int[] a) {
+ return solve(getArcs(), a);
}
private boolean computeHasWeights() {
@@ -1740,28 +1749,18 @@ public class GridLayout extends ViewGroup {
return deltas;
}
- private void shareOutDelta() {
- int totalDelta = 0;
- float totalWeight = 0;
+ private void shareOutDelta(int totalDelta, float totalWeight) {
+ Arrays.fill(deltas, 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
+ // the two adjustments below are to counter the above rounding and avoid
+ // off-by-ones at the end
totalDelta -= delta;
totalWeight -= weight;
}
@@ -1771,12 +1770,46 @@ public class GridLayout extends ViewGroup {
private void solveAndDistributeSpace(int[] a) {
Arrays.fill(getDeltas(), 0);
solve(a);
- shareOutDelta();
- arcsValid = false;
- forwardLinksValid = false;
- backwardLinksValid = false;
- groupBoundsValid = false;
- solve(a);
+ int deltaMax = parentMin.value * getChildCount() + 1; //exclusive
+ if (deltaMax < 2) {
+ return; //don't have any delta to distribute
+ }
+ int deltaMin = 0; //inclusive
+
+ float totalWeight = calculateTotalWeight();
+
+ int validDelta = -1; //delta for which a solution exists
+ boolean validSolution = true;
+ // do a binary search to find the max delta that won't conflict with constraints
+ while(deltaMin < deltaMax) {
+ final int delta = (deltaMin + deltaMax) / 2;
+ invalidateValues();
+ shareOutDelta(delta, totalWeight);
+ validSolution = solve(getArcs(), a, false);
+ if (validSolution) {
+ validDelta = delta;
+ deltaMin = delta + 1;
+ } else {
+ deltaMax = delta;
+ }
+ }
+ if (validDelta > 0 && !validSolution) {
+ // last solution was not successful but we have a successful one. Use it.
+ invalidateValues();
+ shareOutDelta(validDelta, totalWeight);
+ solve(a);
+ }
+ }
+
+ private float calculateTotalWeight() {
+ float totalWeight = 0f;
+ for (int i = 0, N = getChildCount(); i < N; i++) {
+ View c = getChildAt(i);
+ LayoutParams lp = getLayoutParams(c);
+ Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+ totalWeight += spec.weight;
+ }
+ return totalWeight;
}
private void computeLocations(int[] a) {
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 0c65c50..1b93b97 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
@@ -757,7 +758,7 @@ public class HorizontalScrollView extends FrameLayout {
} else {
super.scrollTo(scrollX, scrollY);
}
-
+
awakenScrollBars();
}
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index f90a9fe..c68bfca 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -386,21 +386,21 @@ public class ImageView extends View {
*/
@android.view.RemotableViewMethod
public void setImageResource(int resId) {
- if (mUri != null || mResource != resId) {
- final int oldWidth = mDrawableWidth;
- final int oldHeight = mDrawableHeight;
+ // The resource configuration may have changed, so we should always
+ // try to load the resource even if the resId hasn't changed.
+ final int oldWidth = mDrawableWidth;
+ final int oldHeight = mDrawableHeight;
- updateDrawable(null);
- mResource = resId;
- mUri = null;
+ updateDrawable(null);
+ mResource = resId;
+ mUri = null;
- resolveUri();
+ resolveUri();
- if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
- requestLayout();
- }
- invalidate();
+ if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
+ requestLayout();
}
+ invalidate();
}
/**
@@ -527,6 +527,12 @@ public class ImageView extends View {
if (mHasDrawableTintMode) {
mDrawable.setTintMode(mDrawableTintMode);
}
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mDrawable.isStateful()) {
+ mDrawable.setState(getDrawableState());
+ }
}
}
@@ -820,6 +826,7 @@ public class ImageView extends View {
mDrawableHeight = d.getIntrinsicHeight();
applyImageTint();
applyColorMod();
+
configureBounds();
} else {
mDrawableWidth = mDrawableHeight = -1;
@@ -1120,6 +1127,9 @@ public class ImageView extends View {
/** @hide */
public void animateTransform(Matrix matrix) {
+ if (mDrawable == null) {
+ return;
+ }
if (matrix == null) {
mDrawable.setBounds(0, 0, getWidth(), getHeight());
} else {
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index 3c186e3..d85bbb9 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -1252,14 +1252,7 @@ public class ListPopupWindow {
final boolean wasForwarding = mForwarding;
final boolean forwarding;
if (wasForwarding) {
- if (mWasLongPress) {
- // If we started forwarding as a result of a long-press,
- // just silently stop forwarding events so that the window
- // stays open.
- forwarding = onTouchForwarded(event);
- } else {
- forwarding = onTouchForwarded(event) || !onForwardingStopped();
- }
+ forwarding = onTouchForwarded(event) || !onForwardingStopped();
} else {
forwarding = onTouchObserved(event) && onForwardingStarted();
@@ -1392,7 +1385,9 @@ public class ListPopupWindow {
clearCallbacks();
final View src = mSrc;
- if (!src.isEnabled()) {
+ if (!src.isEnabled() || src.isLongClickable()) {
+ // Ignore long-press if the view is disabled or has its own
+ // handler.
return;
}
@@ -1401,12 +1396,12 @@ public class ListPopupWindow {
}
// Don't let the parent intercept our events.
- mSrc.getParent().requestDisallowInterceptTouchEvent(true);
+ src.getParent().requestDisallowInterceptTouchEvent(true);
// Make sure we cancel any ongoing source event stream.
final long now = SystemClock.uptimeMillis();
final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
- mSrc.onTouchEvent(e);
+ src.onTouchEvent(e);
e.recycle();
mForwarding = true;
@@ -1639,6 +1634,11 @@ public class ListPopupWindow {
setPressed(false);
updateSelectorState();
+ final View motionView = getChildAt(mMotionPosition - mFirstPosition);
+ if (motionView != null) {
+ motionView.setPressed(false);
+ }
+
if (mClickAnimation != null) {
mClickAnimation.cancel();
mClickAnimation = null;
@@ -1648,10 +1648,32 @@ public class ListPopupWindow {
private void setPressedItem(View child, int position, float x, float y) {
mDrawsInPressedState = true;
- // Ordering is essential. First update the pressed state and layout
- // the children. This will ensure the selector actually gets drawn.
- setPressed(true);
- layoutChildren();
+ // Ordering is essential. First, update the container's pressed state.
+ drawableHotspotChanged(x, y);
+ if (!isPressed()) {
+ setPressed(true);
+ }
+
+ // Next, run layout if we need to stabilize child positions.
+ if (mDataChanged) {
+ layoutChildren();
+ }
+
+ // Manage the pressed view based on motion position. This allows us to
+ // play nicely with actual touch and scroll events.
+ final View motionView = getChildAt(mMotionPosition - mFirstPosition);
+ if (motionView != null && motionView != child && motionView.isPressed()) {
+ motionView.setPressed(false);
+ }
+ mMotionPosition = position;
+
+ // Offset for child coordinates.
+ final float childX = x - child.getLeft();
+ final float childY = y - child.getTop();
+ child.drawableHotspotChanged(childX, childY);
+ if (!child.isPressed()) {
+ child.setPressed(true);
+ }
// Ensure that keyboard focus starts from the last touched position.
setSelectedPositionInt(position);
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
index 84b1d13..451e493 100644
--- a/core/java/android/widget/OverScroller.java
+++ b/core/java/android/widget/OverScroller.java
@@ -901,6 +901,10 @@ public class OverScroller {
final long time = AnimationUtils.currentAnimationTimeMillis();
final long currentTime = time - mStartTime;
+ if (currentTime == 0) {
+ // Skip work but report that we're still going if we have a nonzero duration.
+ return mDuration > 0;
+ }
if (currentTime > mDuration) {
return false;
}
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index 111dadc..2708398 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -16,6 +16,7 @@
package android.widget;
+import com.android.internal.R;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuPopupHelper;
import com.android.internal.view.menu.MenuPresenter;
@@ -37,10 +38,11 @@ import android.widget.ListPopupWindow.ForwardingListener;
* of the popup will dismiss it.
*/
public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
- private Context mContext;
- private MenuBuilder mMenu;
- private View mAnchor;
- private MenuPopupHelper mPopup;
+ private final Context mContext;
+ private final MenuBuilder mMenu;
+ private final View mAnchor;
+ private final MenuPopupHelper mPopup;
+
private OnMenuItemClickListener mMenuItemClickListener;
private OnDismissListener mDismissListener;
private OnTouchListener mDragListener;
@@ -58,31 +60,56 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
}
/**
- * Construct a new PopupMenu.
+ * Constructor to create a new popup menu with an anchor view.
*
- * @param context Context for the PopupMenu.
- * @param anchor Anchor view for this popup. The popup will appear below the anchor if there
- * is room, or above it if there is not.
+ * @param context Context the popup menu is running in, through which it
+ * can access the current theme, resources, etc.
+ * @param anchor Anchor view for this popup. The popup will appear below
+ * the anchor if there is room, or above it if there is not.
*/
public PopupMenu(Context context, View anchor) {
this(context, anchor, Gravity.NO_GRAVITY);
}
/**
- * Construct a new PopupMenu.
+ * Constructor to create a new popup menu with an anchor view and alignment
+ * gravity.
*
- * @param context Context for the PopupMenu.
- * @param anchor Anchor view for this popup. The popup will appear below the anchor if there
- * is room, or above it if there is not.
- * @param gravity The {@link Gravity} value for aligning the popup with its anchor
+ * @param context Context the popup menu is running in, through which it
+ * can access the current theme, resources, etc.
+ * @param anchor Anchor view for this popup. The popup will appear below
+ * the anchor if there is room, or above it if there is not.
+ * @param gravity The {@link Gravity} value for aligning the popup with its
+ * anchor.
*/
public PopupMenu(Context context, View anchor, int gravity) {
- // TODO Theme?
+ this(context, anchor, gravity, R.attr.popupMenuStyle, 0);
+ }
+
+ /**
+ * Constructor a create a new popup menu with a specific style.
+ *
+ * @param context Context the popup menu is running in, through which it
+ * can access the current theme, resources, etc.
+ * @param anchor Anchor view for this popup. The popup will appear below
+ * the anchor if there is room, or above it if there is not.
+ * @param gravity The {@link Gravity} value for aligning the popup with its
+ * anchor.
+ * @param popupStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the popup window. Can be 0 to not look for defaults.
+ * @param popupStyleRes A resource identifier of a style resource that
+ * supplies default values for the popup window, used only if
+ * popupStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ */
+ public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr,
+ int popupStyleRes) {
mContext = context;
mMenu = new MenuBuilder(context);
mMenu.setCallback(this);
mAnchor = anchor;
- mPopup = new MenuPopupHelper(context, mMenu, anchor);
+ mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
mPopup.setGravity(gravity);
mPopup.setCallback(this);
}
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index bde6201..5419ab6 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -97,9 +97,11 @@ public class PopupWindow {
private boolean mAllowScrollingAnchorParent = true;
private boolean mLayoutInsetDecor = false;
private boolean mNotTouchModal;
+ private boolean mAttachedInDecor = true;
+ private boolean mAttachedInDecorSet = false;
private OnTouchListener mTouchInterceptor;
-
+
private int mWidthMode;
private int mWidth;
private int mLastWidth;
@@ -196,54 +198,17 @@ public class PopupWindow {
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
final TypedArray a = context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes);
-
- mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
+ attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes);
+ final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0);
mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);
final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1);
- mAnimationStyle = animStyle == com.android.internal.R.style.Animation_PopupWindow ? -1 :
- animStyle;
-
- // If this is a StateListDrawable, try to find and store the drawable to be
- // used when the drop-down is placed above its anchor view, and the one to be
- // used when the drop-down is placed below its anchor view. We extract
- // the drawables ourselves to work around a problem with using refreshDrawableState
- // that it will take into account the padding of all drawables specified in a
- // StateListDrawable, thus adding superfluous padding to drop-down views.
- //
- // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and
- // at least one other drawable, intended for the 'below-anchor state'.
- if (mBackground instanceof StateListDrawable) {
- StateListDrawable background = (StateListDrawable) mBackground;
+ mAnimationStyle = animStyle == R.style.Animation_PopupWindow ? -1 : animStyle;
- // Find the above-anchor view - this one's easy, it should be labeled as such.
- int aboveAnchorStateIndex = background.getStateDrawableIndex(ABOVE_ANCHOR_STATE_SET);
-
- // Now, for the below-anchor view, look for any other drawable specified in the
- // StateListDrawable which is not for the above-anchor state and use that.
- int count = background.getStateCount();
- int belowAnchorStateIndex = -1;
- for (int i = 0; i < count; i++) {
- if (i != aboveAnchorStateIndex) {
- belowAnchorStateIndex = i;
- break;
- }
- }
-
- // Store the drawables we found, if we found them. Otherwise, set them both
- // to null so that we'll just use refreshDrawableState.
- if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) {
- mAboveAnchorBackgroundDrawable = background.getStateDrawable(aboveAnchorStateIndex);
- mBelowAnchorBackgroundDrawable = background.getStateDrawable(belowAnchorStateIndex);
- } else {
- mBelowAnchorBackgroundDrawable = null;
- mAboveAnchorBackgroundDrawable = null;
- }
- }
-
a.recycle();
+
+ setBackgroundDrawable(bg);
}
/**
@@ -316,6 +281,7 @@ public class PopupWindow {
mContext = contentView.getContext();
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
+
setContentView(contentView);
setWidth(width);
setHeight(height);
@@ -343,6 +309,43 @@ public class PopupWindow {
*/
public void setBackgroundDrawable(Drawable background) {
mBackground = background;
+
+ // If this is a StateListDrawable, try to find and store the drawable to be
+ // used when the drop-down is placed above its anchor view, and the one to be
+ // used when the drop-down is placed below its anchor view. We extract
+ // the drawables ourselves to work around a problem with using refreshDrawableState
+ // that it will take into account the padding of all drawables specified in a
+ // StateListDrawable, thus adding superfluous padding to drop-down views.
+ //
+ // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and
+ // at least one other drawable, intended for the 'below-anchor state'.
+ if (mBackground instanceof StateListDrawable) {
+ StateListDrawable stateList = (StateListDrawable) mBackground;
+
+ // Find the above-anchor view - this one's easy, it should be labeled as such.
+ int aboveAnchorStateIndex = stateList.getStateDrawableIndex(ABOVE_ANCHOR_STATE_SET);
+
+ // Now, for the below-anchor view, look for any other drawable specified in the
+ // StateListDrawable which is not for the above-anchor state and use that.
+ int count = stateList.getStateCount();
+ int belowAnchorStateIndex = -1;
+ for (int i = 0; i < count; i++) {
+ if (i != aboveAnchorStateIndex) {
+ belowAnchorStateIndex = i;
+ break;
+ }
+ }
+
+ // Store the drawables we found, if we found them. Otherwise, set them both
+ // to null so that we'll just use refreshDrawableState.
+ if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) {
+ mAboveAnchorBackgroundDrawable = stateList.getStateDrawable(aboveAnchorStateIndex);
+ mBelowAnchorBackgroundDrawable = stateList.getStateDrawable(belowAnchorStateIndex);
+ } else {
+ mBelowAnchorBackgroundDrawable = null;
+ mAboveAnchorBackgroundDrawable = null;
+ }
+ }
}
/**
@@ -373,7 +376,7 @@ public class PopupWindow {
public int getAnimationStyle() {
return mAnimationStyle;
}
-
+
/**
* Set the flag on popup to ignore cheek press events; by default this flag
* is set to false
@@ -382,7 +385,7 @@ public class PopupWindow {
* <p>If the popup is showing, calling this method will take effect only
* the next time the popup is shown or through a manual call to one of
* the {@link #update()} methods.</p>
- *
+ *
* @see #update()
*/
public void setIgnoreCheekPress() {
@@ -443,6 +446,19 @@ public class PopupWindow {
if (mWindowManager == null && mContentView != null) {
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
+
+ // Setting the default for attachedInDecor based on SDK version here
+ // instead of in the constructor since we might not have the context
+ // object in the constructor. We only want to set default here if the
+ // app hasn't already set the attachedInDecor.
+ if (mContext != null && !mAttachedInDecorSet) {
+ // Attach popup window in decor frame of parent window by default for
+ // {@link Build.VERSION_CODES.LOLLIPOP_MR1} or greater. Keep current
+ // behavior of not attaching to decor frame for older SDKs.
+ setAttachedInDecor(mContext.getApplicationInfo().targetSdkVersion
+ >= Build.VERSION_CODES.LOLLIPOP_MR1);
+ }
+
}
/**
@@ -452,7 +468,7 @@ public class PopupWindow {
public void setTouchInterceptor(OnTouchListener l) {
mTouchInterceptor = l;
}
-
+
/**
* <p>Indicate whether the popup window can grab the focus.</p>
*
@@ -702,6 +718,36 @@ public class PopupWindow {
}
/**
+ * <p>Indicates whether the popup window will be attached in the decor frame of its parent
+ * window.
+ *
+ * @return true if the window will be attached to the decor frame of its parent window.
+ *
+ * @see #setAttachedInDecor(boolean)
+ * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR
+ */
+ public boolean isAttachedInDecor() {
+ return mAttachedInDecor;
+ }
+
+ /**
+ * <p>This will attach the popup window to the decor frame of the parent window to avoid
+ * overlaping with screen decorations like the navigation bar. Overrides the default behavior of
+ * the flag {@link WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR}.
+ *
+ * <p>By default the flag is set on SDK version {@link Build.VERSION_CODES#LOLLIPOP_MR1} or
+ * greater and cleared on lesser SDK versions.
+ *
+ * @param enabled true if the popup should be attached to the decor frame of its parent window.
+ *
+ * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR
+ */
+ public void setAttachedInDecor(boolean enabled) {
+ mAttachedInDecor = enabled;
+ mAttachedInDecorSet = true;
+ }
+
+ /**
* Allows the popup window to force the flag
* {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}, overriding default behavior.
* This will cause the popup to inset its content to account for system windows overlaying
@@ -1140,9 +1186,12 @@ public class PopupWindow {
if (mNotTouchModal) {
curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
+ if (mAttachedInDecor) {
+ curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
+ }
return curFlags;
}
-
+
private int computeAnimationResource() {
if (mAnimationStyle == -1) {
if (mIsDropdown) {
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index e9298c2..de1bbc7 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -227,8 +227,6 @@ public class ProgressBar extends View {
private long mUiThreadId;
private boolean mShouldStartAnimationDrawable;
- private float mAnimationPosition;
-
private boolean mInDrawing;
private boolean mAttached;
private boolean mRefreshIsPosted;
@@ -246,7 +244,7 @@ public class ProgressBar extends View {
public ProgressBar(Context context) {
this(context, null);
}
-
+
public ProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.progressBarStyle);
}
@@ -263,9 +261,9 @@ public class ProgressBar extends View {
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes);
-
+
mNoInvalidate = true;
-
+
final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
if (progressDrawable != null) {
// Calling this method can set mMaxHeight, make sure the corresponding
@@ -284,11 +282,11 @@ public class ProgressBar extends View {
mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior);
final int resID = a.getResourceId(
- com.android.internal.R.styleable.ProgressBar_interpolator,
+ com.android.internal.R.styleable.ProgressBar_interpolator,
android.R.anim.linear_interpolator); // default to linear interpolator
if (resID > 0) {
setInterpolator(context, resID);
- }
+ }
setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
@@ -401,12 +399,12 @@ public class ProgressBar extends View {
* traverse layer and state list drawables.
*/
private Drawable tileify(Drawable drawable, boolean clip) {
-
+
if (drawable instanceof LayerDrawable) {
LayerDrawable background = (LayerDrawable) drawable;
final int N = background.getNumberOfLayers();
Drawable[] outDrawables = new Drawable[N];
-
+
for (int i = 0; i < N; i++) {
int id = background.getId(i);
outDrawables[i] = tileify(background.getDrawable(i),
@@ -414,13 +412,13 @@ public class ProgressBar extends View {
}
LayerDrawable newBg = new LayerDrawable(outDrawables);
-
+
for (int i = 0; i < N; i++) {
newBg.setId(i, background.getId(i));
}
-
+
return newBg;
-
+
} else if (drawable instanceof StateListDrawable) {
StateListDrawable in = (StateListDrawable) drawable;
StateListDrawable out = new StateListDrawable();
@@ -429,7 +427,7 @@ public class ProgressBar extends View {
out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
}
return out;
-
+
} else if (drawable instanceof BitmapDrawable) {
final BitmapDrawable bitmap = (BitmapDrawable) drawable;
final Bitmap tileBitmap = bitmap.getBitmap();
@@ -450,7 +448,7 @@ public class ProgressBar extends View {
return clip ? new ClipDrawable(
shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL) : shapeDrawable;
}
-
+
return drawable;
}
@@ -458,7 +456,7 @@ public class ProgressBar extends View {
final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
return new RoundRectShape(roundedCorners, null, null);
}
-
+
/**
* Convert a AnimationDrawable for use as a barberpole animation.
* Each frame of the animation is wrapped in a ClipDrawable and
@@ -470,7 +468,7 @@ public class ProgressBar extends View {
final int N = background.getNumberOfFrames();
AnimationDrawable newBg = new AnimationDrawable();
newBg.setOneShot(background.isOneShot());
-
+
for (int i = 0; i < N; i++) {
Drawable frame = tileify(background.getFrame(i), true);
frame.setLevel(10000);
@@ -481,7 +479,7 @@ public class ProgressBar extends View {
}
return drawable;
}
-
+
/**
* <p>
* Initialize the progress bar's default values:
@@ -522,7 +520,7 @@ public class ProgressBar extends View {
* <p>Change the indeterminate mode for this progress bar. In indeterminate
* mode, the progress is ignored and the progress bar shows an infinite
* animation instead.</p>
- *
+ *
* If this progress bar's style only supports indeterminate mode (such as the circular
* progress bars), then this will be ignored.
*
@@ -604,6 +602,7 @@ public class ProgressBar extends View {
* @see #getIndeterminateTintList()
* @see Drawable#setTintList(ColorStateList)
*/
+ @RemotableViewMethod
public void setIndeterminateTintList(@Nullable ColorStateList tint) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
@@ -672,6 +671,12 @@ public class ProgressBar extends View {
if (tintInfo.mHasIndeterminateTintMode) {
mIndeterminateDrawable.setTintMode(tintInfo.mIndeterminateTintMode);
}
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mIndeterminateDrawable.isStateful()) {
+ mIndeterminateDrawable.setState(getDrawableState());
+ }
}
}
}
@@ -694,7 +699,7 @@ public class ProgressBar extends View {
setIndeterminateDrawable(d);
}
-
+
/**
* <p>Get the drawable used to draw the progress bar in
* progress mode.</p>
@@ -780,6 +785,12 @@ public class ProgressBar extends View {
if (mProgressTintInfo.mHasProgressTintMode) {
target.setTintMode(mProgressTintInfo.mProgressTintMode);
}
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (target.isStateful()) {
+ target.setState(getDrawableState());
+ }
}
}
}
@@ -799,6 +810,12 @@ public class ProgressBar extends View {
if (mProgressTintInfo.mHasProgressBackgroundTintMode) {
target.setTintMode(mProgressTintInfo.mProgressBackgroundTintMode);
}
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (target.isStateful()) {
+ target.setState(getDrawableState());
+ }
}
}
}
@@ -818,6 +835,12 @@ public class ProgressBar extends View {
if (mProgressTintInfo.mHasSecondaryProgressTintMode) {
target.setTintMode(mProgressTintInfo.mSecondaryProgressTintMode);
}
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (target.isStateful()) {
+ target.setState(getDrawableState());
+ }
}
}
}
@@ -842,6 +865,7 @@ public class ProgressBar extends View {
* @see #getProgressTintList()
* @see Drawable#setTintList(ColorStateList)
*/
+ @RemotableViewMethod
public void setProgressTintList(@Nullable ColorStateList tint) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
@@ -923,6 +947,7 @@ public class ProgressBar extends View {
* @see #getProgressBackgroundTintList()
* @see Drawable#setTintList(ColorStateList)
*/
+ @RemotableViewMethod
public void setProgressBackgroundTintList(@Nullable ColorStateList tint) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
@@ -1110,7 +1135,7 @@ public class ProgressBar extends View {
setProgressDrawable(d);
}
-
+
/**
* @return The drawable currently used to draw the progress bar
*/
@@ -1161,7 +1186,7 @@ public class ProgressBar extends View {
final int count = mRefreshData.size();
for (int i = 0; i < count; i++) {
final RefreshData rd = mRefreshData.get(i);
- doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate);
+ doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
rd.recycle();
}
mRefreshData.clear();
@@ -1176,12 +1201,10 @@ public class ProgressBar extends View {
new SynchronizedPool<RefreshData>(POOL_MAX);
public int id;
- public float progress;
+ public int progress;
public boolean fromUser;
- public boolean animate;
- public static RefreshData obtain(int id, float progress, boolean fromUser,
- boolean animate) {
+ public static RefreshData obtain(int id, int progress, boolean fromUser) {
RefreshData rd = sPool.acquire();
if (rd == null) {
rd = new RefreshData();
@@ -1189,10 +1212,9 @@ public class ProgressBar extends View {
rd.id = id;
rd.progress = progress;
rd.fromUser = fromUser;
- rd.animate = animate;
return rd;
}
-
+
public void recycle() {
sPool.release(this);
}
@@ -1216,19 +1238,9 @@ public class ProgressBar extends View {
layer.setTintMode(tintMode);
}
- private float getScale(float progress) {
- return mMax > 0 ? progress / (float) mMax : 0;
- }
-
- private synchronized void doRefreshProgress(int id, float progress, boolean fromUser,
+ private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
boolean callBackToApp) {
- doRefreshProgress(id, progress, fromUser, callBackToApp, false);
- }
-
- private synchronized void doRefreshProgress(int id, float progress, boolean fromUser,
- boolean callBackToApp, boolean animate) {
- float scale = getScale(progress);
-
+ float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
final Drawable d = mCurrentDrawable;
if (d != null) {
Drawable progressDrawable = null;
@@ -1245,65 +1257,27 @@ public class ProgressBar extends View {
} else {
invalidate();
}
-
- if (id == R.id.progress) {
- if (animate) {
- onAnimatePosition(scale, fromUser);
- } else if (callBackToApp) {
- onProgressRefresh(scale, fromUser);
- }
+
+ if (callBackToApp && id == R.id.progress) {
+ onProgressRefresh(scale, fromUser);
}
}
- /**
- * Called when a ProgressBar is animating its position.
- *
- * @param scale Current position/progress between 0 and 1.
- * @param fromUser True if the progress change was initiated by the user.
- */
- void onAnimatePosition(float scale, boolean fromUser) {
- }
-
- /**
- * Sets the progress value without going through the entire refresh process.
- *
- * @see #setProgress(int, boolean)
- * @param progress The new progress, between 0 and {@link #getMax()}
- */
- void setProgressValueOnly(int progress) {
- mProgress = progress;
- onProgressRefresh(getScale(progress), true);
- }
-
- void setAnimationPosition(float position) {
- mAnimationPosition = position;
- refreshProgress(R.id.progress, position, true, true);
- }
-
- float getAnimationPosition() {
- return mAnimationPosition;
- }
-
void onProgressRefresh(float scale, boolean fromUser) {
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
scheduleAccessibilityEventSender();
}
}
- private synchronized void refreshProgress(int id, float progress, boolean fromUser) {
- refreshProgress(id, progress, fromUser, false);
- }
-
- private synchronized void refreshProgress(int id, float progress, boolean fromUser,
- boolean animate) {
+ private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
if (mUiThreadId == Thread.currentThread().getId()) {
- doRefreshProgress(id, progress, fromUser, true, animate);
+ doRefreshProgress(id, progress, fromUser, true);
} else {
if (mRefreshProgressRunnable == null) {
mRefreshProgressRunnable = new RefreshProgressRunnable();
}
- final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate);
+ final RefreshData rd = RefreshData.obtain(id, progress, fromUser);
mRefreshData.add(rd);
if (mAttached && !mRefreshIsPosted) {
post(mRefreshProgressRunnable);
@@ -1311,7 +1285,7 @@ public class ProgressBar extends View {
}
}
}
-
+
/**
* <p>Set the current progress to the specified value. Does not do anything
* if the progress bar is in indeterminate mode.</p>
@@ -1321,13 +1295,13 @@ public class ProgressBar extends View {
* @see #setIndeterminate(boolean)
* @see #isIndeterminate()
* @see #getProgress()
- * @see #incrementProgressBy(int)
+ * @see #incrementProgressBy(int)
*/
@android.view.RemotableViewMethod
public synchronized void setProgress(int progress) {
setProgress(progress, false);
}
-
+
@android.view.RemotableViewMethod
synchronized void setProgress(int progress, boolean fromUser) {
if (mIndeterminate) {
@@ -1353,7 +1327,7 @@ public class ProgressBar extends View {
* Set the current secondary progress to the specified value. Does not do
* anything if the progress bar is in indeterminate mode.
* </p>
- *
+ *
* @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
* @see #setIndeterminate(boolean)
* @see #isIndeterminate()
@@ -1434,8 +1408,8 @@ public class ProgressBar extends View {
* @param max the upper range of this progress bar
*
* @see #getMax()
- * @see #setProgress(int)
- * @see #setSecondaryProgress(int)
+ * @see #setProgress(int)
+ * @see #setSecondaryProgress(int)
*/
@android.view.RemotableViewMethod
public synchronized void setMax(int max) {
@@ -1452,13 +1426,13 @@ public class ProgressBar extends View {
refreshProgress(R.id.progress, mProgress, false);
}
}
-
+
/**
* <p>Increase the progress bar's progress by the specified amount.</p>
*
* @param diff the amount by which the progress must be increased
*
- * @see #setProgress(int)
+ * @see #setProgress(int)
*/
public synchronized final void incrementProgressBy(int diff) {
setProgress(mProgress + diff);
@@ -1469,7 +1443,7 @@ public class ProgressBar extends View {
*
* @param diff the amount by which the secondary progress must be increased
*
- * @see #setSecondaryProgress(int)
+ * @see #setSecondaryProgress(int)
*/
public synchronized final void incrementSecondaryProgressBy(int diff) {
setSecondaryProgress(mSecondaryProgress + diff);
@@ -1492,13 +1466,13 @@ public class ProgressBar extends View {
if (mInterpolator == null) {
mInterpolator = new LinearInterpolator();
}
-
+
if (mTransformation == null) {
mTransformation = new Transformation();
} else {
mTransformation.clear();
}
-
+
if (mAnimation == null) {
mAnimation = new AlphaAnimation(0.0f, 1.0f);
} else {
@@ -1649,7 +1623,7 @@ public class ProgressBar extends View {
}
mIndeterminateDrawable.setBounds(left, top, right, bottom);
}
-
+
if (mProgressDrawable != null) {
mProgressDrawable.setBounds(0, 0, right, bottom);
}
@@ -1719,20 +1693,20 @@ public class ProgressBar extends View {
setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
resolveSizeAndState(dh, heightMeasureSpec, 0));
}
-
+
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
updateDrawableState();
}
-
+
private void updateDrawableState() {
int[] state = getDrawableState();
-
+
if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
mProgressDrawable.setState(state);
}
-
+
if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
mIndeterminateDrawable.setState(state);
}
@@ -1754,14 +1728,14 @@ public class ProgressBar extends View {
static class SavedState extends BaseSavedState {
int progress;
int secondaryProgress;
-
+
/**
* Constructor called from {@link ProgressBar#onSaveInstanceState()}
*/
SavedState(Parcelable superState) {
super(superState);
}
-
+
/**
* Constructor called from {@link #CREATOR}
*/
@@ -1795,10 +1769,10 @@ public class ProgressBar extends View {
// Force our ancestor class to save its state
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
-
+
ss.progress = mProgress;
ss.secondaryProgress = mSecondaryProgress;
-
+
return ss;
}
@@ -1806,7 +1780,7 @@ public class ProgressBar extends View {
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
-
+
setProgress(ss.progress);
setSecondaryProgress(ss.secondaryProgress);
}
@@ -1822,7 +1796,7 @@ public class ProgressBar extends View {
final int count = mRefreshData.size();
for (int i = 0; i < count; i++) {
final RefreshData rd = mRefreshData.get(i);
- doRefreshProgress(rd.id, rd.progress, rd.fromUser, rd.animate);
+ doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
rd.recycle();
}
mRefreshData.clear();
diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index 56f126c..11fda2c 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -22,21 +22,19 @@ import android.animation.Keyframe;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
-import android.annotation.SuppressLint;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.Rect;
import android.graphics.Typeface;
-import android.graphics.RectF;
import android.os.Bundle;
-import android.text.format.DateUtils;
-import android.text.format.Time;
import android.util.AttributeSet;
+import android.util.IntArray;
import android.util.Log;
+import android.util.MathUtils;
import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
@@ -44,10 +42,11 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.internal.R;
+import com.android.internal.widget.ExploreByTouchHelper;
-import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Locale;
@@ -69,7 +68,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private static final int HOURS = 0;
private static final int MINUTES = 1;
private static final int HOURS_INNER = 2;
- private static final int AMPM = 3;
private static final int SELECTOR_CIRCLE = 0;
private static final int SELECTOR_DOT = 1;
@@ -87,12 +85,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
// Alpha level of color for selector.
private static final int ALPHA_SELECTOR = 60; // was 51
- // Alpha level of color for selected circle.
- private static final int ALPHA_AMPM_SELECTED = ALPHA_SELECTOR;
-
- // Alpha level of color for pressed circle.
- private static final int ALPHA_AMPM_PRESSED = 255; // was 175
-
private static final float COSINE_30_DEGREES = ((float) Math.sqrt(3)) * 0.5f;
private static final float SINE_30_DEGREES = 0.5f;
@@ -105,17 +97,16 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private static final int CENTER_RADIUS = 2;
- private static final int[] STATE_SET_SELECTED = new int[] {R.attr.state_selected};
-
private static int[] sSnapPrefer30sMap = new int[361];
+ private final InvalidateUpdateListener mInvalidateUpdateListener =
+ new InvalidateUpdateListener();
+
private final String[] mHours12Texts = new String[12];
private final String[] mOuterHours24Texts = new String[12];
private final String[] mInnerHours24Texts = new String[12];
private final String[] mMinutesTexts = new String[12];
- private final String[] mAmPmText = new String[2];
-
private final Paint[] mPaint = new Paint[2];
private final int[] mColor = new int[2];
private final IntHolder[] mAlpha = new IntHolder[2];
@@ -126,14 +117,42 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private final int[][] mColorSelector = new int[2][3];
private final IntHolder[][] mAlphaSelector = new IntHolder[2][3];
- private final Paint mPaintAmPmText = new Paint();
- private final Paint[] mPaintAmPmCircle = new Paint[2];
-
private final Paint mPaintBackground = new Paint();
- private final Paint mPaintDisabled = new Paint();
private final Paint mPaintDebug = new Paint();
- private Typeface mTypeface;
+ private final Typeface mTypeface;
+
+ private final float[] mCircleRadius = new float[3];
+
+ private final float[] mTextSize = new float[2];
+
+ private final float[][] mTextGridHeights = new float[2][7];
+ private final float[][] mTextGridWidths = new float[2][7];
+
+ private final float[] mInnerTextGridHeights = new float[7];
+ private final float[] mInnerTextGridWidths = new float[7];
+
+ private final float[] mCircleRadiusMultiplier = new float[2];
+ private final float[] mNumbersRadiusMultiplier = new float[3];
+
+ private final float[] mTextSizeMultiplier = new float[3];
+
+ private final float[] mAnimationRadiusMultiplier = new float[3];
+
+ private final float mTransitionMidRadiusMultiplier;
+ private final float mTransitionEndRadiusMultiplier;
+
+ private final int[] mLineLength = new int[3];
+ private final int[] mSelectionRadius = new int[3];
+ private final float mSelectionRadiusMultiplier;
+ private final int[] mSelectionDegrees = new int[3];
+
+ private final ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<Animator>();
+ private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<Animator>();
+
+ private final RadialPickerTouchHelper mTouchHelper;
+
+ private float mInnerTextSize;
private boolean mIs24HourMode;
private boolean mShowHours;
@@ -147,66 +166,21 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private int mXCenter;
private int mYCenter;
- private float[] mCircleRadius = new float[3];
-
private int mMinHypotenuseForInnerNumber;
private int mMaxHypotenuseForOuterNumber;
private int mHalfwayHypotenusePoint;
- private float[] mTextSize = new float[2];
- private float mInnerTextSize;
-
- private float[][] mTextGridHeights = new float[2][7];
- private float[][] mTextGridWidths = new float[2][7];
-
- private float[] mInnerTextGridHeights = new float[7];
- private float[] mInnerTextGridWidths = new float[7];
-
private String[] mOuterTextHours;
private String[] mInnerTextHours;
private String[] mOuterTextMinutes;
-
- private float[] mCircleRadiusMultiplier = new float[2];
- private float[] mNumbersRadiusMultiplier = new float[3];
-
- private float[] mTextSizeMultiplier = new float[3];
-
- private float[] mAnimationRadiusMultiplier = new float[3];
-
- private float mTransitionMidRadiusMultiplier;
- private float mTransitionEndRadiusMultiplier;
-
private AnimatorSet mTransition;
- private InvalidateUpdateListener mInvalidateUpdateListener = new InvalidateUpdateListener();
-
- private int[] mLineLength = new int[3];
- private int[] mSelectionRadius = new int[3];
- private float mSelectionRadiusMultiplier;
- private int[] mSelectionDegrees = new int[3];
-
- private int mAmPmCircleRadius;
- private float mAmPmYCenter;
-
- private float mAmPmCircleRadiusMultiplier;
- private int mAmPmTextColor;
-
- private float mLeftIndicatorXCenter;
- private float mRightIndicatorXCenter;
-
- private int mAmPmUnselectedColor;
- private int mAmPmSelectedColor;
private int mAmOrPm;
- private int mAmOrPmPressed;
-
private int mDisabledAlpha;
- private RectF mRectF = new RectF();
- private boolean mInputEnabled = true;
private OnValueSelectedListener mListener;
- private final ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<Animator>();
- private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<Animator>();
+ private boolean mInputEnabled = true;
public interface OnValueSelectedListener {
void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
@@ -314,11 +288,21 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
return degrees;
}
+ @SuppressWarnings("unused")
+ public RadialTimePickerView(Context context) {
+ this(context, null);
+ }
+
public RadialTimePickerView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.timePickerStyle);
}
- public RadialTimePickerView(Context context, AttributeSet attrs, int defStyle) {
+ public RadialTimePickerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public RadialTimePickerView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs);
// Pull disabled alpha from theme.
@@ -329,28 +313,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
// process style attributes
final Resources res = getResources();
final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TimePicker,
- defStyle, 0);
-
- ColorStateList amPmBackgroundColor = a.getColorStateList(
- R.styleable.TimePicker_amPmBackgroundColor);
- if (amPmBackgroundColor == null) {
- amPmBackgroundColor = res.getColorStateList(
- R.color.timepicker_default_ampm_unselected_background_color_material);
- }
-
- // Obtain the backup selected color. If the background color state
- // list doesn't have a state for selected, we'll use this color.
- final int amPmSelectedColor = a.getColor(R.styleable.TimePicker_amPmSelectedBackgroundColor,
- res.getColor(R.color.timepicker_default_ampm_selected_background_color_material));
- amPmBackgroundColor = ColorStateList.addFirstIfMissing(
- amPmBackgroundColor, R.attr.state_selected, amPmSelectedColor);
-
- mAmPmSelectedColor = amPmBackgroundColor.getColorForState(
- STATE_SET_SELECTED, amPmSelectedColor);
- mAmPmUnselectedColor = amPmBackgroundColor.getDefaultColor();
-
- mAmPmTextColor = a.getColor(R.styleable.TimePicker_amPmTextColor,
- res.getColor(R.color.timepicker_default_text_color_material));
+ defStyleAttr, defStyleRes);
mTypeface = Typeface.create("sans-serif", Typeface.NORMAL);
@@ -419,16 +382,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
R.styleable.TimePicker_numbersSelectorColor,
R.color.timepicker_default_selector_color_material);
- mPaintAmPmText.setColor(mAmPmTextColor);
- mPaintAmPmText.setTypeface(mTypeface);
- mPaintAmPmText.setAntiAlias(true);
- mPaintAmPmText.setTextAlign(Paint.Align.CENTER);
-
- mPaintAmPmCircle[AM] = new Paint();
- mPaintAmPmCircle[AM].setAntiAlias(true);
- mPaintAmPmCircle[PM] = new Paint();
- mPaintAmPmCircle[PM].setAntiAlias(true);
-
mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor,
res.getColor(R.color.timepicker_default_numbers_background_color_material)));
mPaintBackground.setAntiAlias(true);
@@ -444,7 +397,14 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
mShowHours = true;
mIs24HourMode = false;
mAmOrPm = AM;
- mAmOrPmPressed = -1;
+
+ // Set up accessibility components.
+ mTouchHelper = new RadialPickerTouchHelper();
+ setAccessibilityDelegate(mTouchHelper);
+
+ if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
initHoursAndMinutesText();
initData();
@@ -470,8 +430,8 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
final int currentMinute = calendar.get(Calendar.MINUTE);
- setCurrentHour(currentHour);
- setCurrentMinute(currentMinute);
+ setCurrentHourInternal(currentHour, false, false);
+ setCurrentMinuteInternal(currentMinute, false);
setHapticFeedbackEnabled(true);
}
@@ -492,9 +452,13 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
}
public void initialize(int hour, int minute, boolean is24HourMode) {
- mIs24HourMode = is24HourMode;
- setCurrentHour(hour);
- setCurrentMinute(minute);
+ if (mIs24HourMode != is24HourMode) {
+ mIs24HourMode = is24HourMode;
+ initData();
+ }
+
+ setCurrentHourInternal(hour, false, false);
+ setCurrentMinuteInternal(minute, false);
}
public void setCurrentItemShowing(int item, boolean animate) {
@@ -524,23 +488,39 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
* @param hour the current hour between 0 and 23 (inclusive)
*/
public void setCurrentHour(int hour) {
+ setCurrentHourInternal(hour, true, false);
+ }
+
+ /**
+ * Sets the current hour.
+ *
+ * @param hour The current hour
+ * @param callback Whether the value listener should be invoked
+ * @param autoAdvance Whether the listener should auto-advance to the next
+ * selection mode, e.g. hour to minutes
+ */
+ private void setCurrentHourInternal(int hour, boolean callback, boolean autoAdvance) {
final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR;
mSelectionDegrees[HOURS] = degrees;
mSelectionDegrees[HOURS_INNER] = degrees;
// 0 is 12 AM (midnight) and 12 is 12 PM (noon).
- mAmOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM;
-
- if (mIs24HourMode) {
- // Inner circle is 1 through 12.
- mIsOnInnerCircle = hour >= 1 && hour <= 12;
- } else {
- mIsOnInnerCircle = false;
+ final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM;
+ final boolean isOnInnerCircle = mIs24HourMode && hour >= 1 && hour <= 12;
+ if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) {
+ mAmOrPm = amOrPm;
+ mIsOnInnerCircle = isOnInnerCircle;
+
+ initData();
+ updateLayoutData();
+ mTouchHelper.invalidateRoot();
}
- initData();
- updateLayoutData();
invalidate();
+
+ if (callback && mListener != null) {
+ mListener.onValueSelected(HOURS, hour, autoAdvance);
+ }
}
/**
@@ -549,15 +529,19 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
* @return the current hour between 0 and 23 (inclusive)
*/
public int getCurrentHour() {
- int hour = (mSelectionDegrees[mIsOnInnerCircle ?
- HOURS_INNER : HOURS] / DEGREES_FOR_ONE_HOUR) % 12;
+ return getHourForDegrees(
+ mSelectionDegrees[mIsOnInnerCircle ? HOURS_INNER : HOURS], mIsOnInnerCircle);
+ }
+
+ private int getHourForDegrees(int degrees, boolean innerCircle) {
+ int hour = (degrees / DEGREES_FOR_ONE_HOUR) % 12;
if (mIs24HourMode) {
// Convert the 12-hour value into 24-hour time based on where the
// selector is positioned.
- if (mIsOnInnerCircle && hour == 0) {
+ if (innerCircle && hour == 0) {
// Inner circle is 1 through 12.
hour = 12;
- } else if (!mIsOnInnerCircle && hour != 0) {
+ } else if (!innerCircle && hour != 0) {
// Outer circle is 13 through 23 and 0.
hour += 12;
}
@@ -567,30 +551,55 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
return hour;
}
+ private int getDegreesForHour(int hour) {
+ // Convert to be 0-11.
+ if (mIs24HourMode) {
+ if (hour >= 12) {
+ hour -= 12;
+ }
+ } else if (hour == 12) {
+ hour = 0;
+ }
+ return hour * DEGREES_FOR_ONE_HOUR;
+ }
+
public void setCurrentMinute(int minute) {
+ setCurrentMinuteInternal(minute, true);
+ }
+
+ private void setCurrentMinuteInternal(int minute, boolean callback) {
mSelectionDegrees[MINUTES] = (minute % 60) * DEGREES_FOR_ONE_MINUTE;
+
invalidate();
+
+ if (callback && mListener != null) {
+ mListener.onValueSelected(MINUTES, minute, false);
+ }
}
// Returns minutes in 0-59 range
public int getCurrentMinute() {
- return (mSelectionDegrees[MINUTES] / DEGREES_FOR_ONE_MINUTE);
+ return getMinuteForDegrees(mSelectionDegrees[MINUTES]);
+ }
+
+ private int getMinuteForDegrees(int degrees) {
+ return degrees / DEGREES_FOR_ONE_MINUTE;
+ }
+
+ private int getDegreesForMinute(int minute) {
+ return minute * DEGREES_FOR_ONE_MINUTE;
}
public void setAmOrPm(int val) {
mAmOrPm = (val % 2);
invalidate();
+ mTouchHelper.invalidateRoot();
}
public int getAmOrPm() {
return mAmOrPm;
}
- public void swapAmPm() {
- mAmOrPm = (mAmOrPm == AM) ? PM : AM;
- invalidate();
- }
-
public void showHours(boolean animate) {
if (mShowHours) return;
mShowHours = true;
@@ -621,10 +630,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
mInnerHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
mMinutesTexts[i] = String.format("%02d", MINUTES_NUMBERS[i]);
}
-
- String[] amPmStrings = TimePickerClockDelegate.getAmPmStrings(mContext);
- mAmPmText[AM] = amPmStrings[0];
- mAmPmText[PM] = amPmStrings[1];
}
private void initData() {
@@ -674,9 +679,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
mAnimationRadiusMultiplier[HOURS_INNER] = 1;
mAnimationRadiusMultiplier[MINUTES] = 1;
- mAmPmCircleRadiusMultiplier = Float.parseFloat(
- res.getString(R.string.timepicker_ampm_circle_radius_multiplier));
-
mAlpha[HOURS].setValue(mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT);
mAlpha[MINUTES].setValue(mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE);
@@ -710,14 +712,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
mCircleRadius[HOURS_INNER] = min * mCircleRadiusMultiplier[HOURS];
mCircleRadius[MINUTES] = min * mCircleRadiusMultiplier[MINUTES];
- if (!mIs24HourMode) {
- // We'll need to draw the AM/PM circles, so the main circle will need to have
- // a slightly higher center. To keep the entire view centered vertically, we'll
- // have to push it up by half the radius of the AM/PM circles.
- int amPmCircleRadius = (int) (mCircleRadius[HOURS] * mAmPmCircleRadiusMultiplier);
- mYCenter -= amPmCircleRadius / 2;
- }
-
mMinHypotenuseForInnerNumber = (int) (mCircleRadius[HOURS]
* mNumbersRadiusMultiplier[HOURS_INNER]) - mSelectionRadius[HOURS];
mMaxHypotenuseForOuterNumber = (int) (mCircleRadius[HOURS]
@@ -739,16 +733,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
mSelectionRadius[HOURS_INNER] = mSelectionRadius[HOURS];
mSelectionRadius[MINUTES] = (int) (mCircleRadius[MINUTES] * mSelectionRadiusMultiplier);
- mAmPmCircleRadius = (int) (mCircleRadius[HOURS] * mAmPmCircleRadiusMultiplier);
- mPaintAmPmText.setTextSize(mAmPmCircleRadius * 3 / 4);
-
- // Line up the vertical center of the AM/PM circles with the bottom of the main circle.
- mAmPmYCenter = mYCenter + mCircleRadius[HOURS];
-
- // Line up the horizontal edges of the AM/PM circles with the horizontal edges
- // of the main circle
- mLeftIndicatorXCenter = mXCenter - mCircleRadius[HOURS] + mAmPmCircleRadius;
- mRightIndicatorXCenter = mXCenter + mCircleRadius[HOURS] - mAmPmCircleRadius;
+ mTouchHelper.invalidateRoot();
}
@Override
@@ -780,9 +765,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
mColor[MINUTES], mAlpha[MINUTES].getValue());
drawCenter(canvas);
- if (!mIs24HourMode) {
- drawAmPm(canvas);
- }
if (DEBUG) {
drawDebug(canvas);
@@ -804,50 +786,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
drawSelector(canvas, MINUTES);
}
- private void drawAmPm(Canvas canvas) {
- final boolean isLayoutRtl = isLayoutRtl();
-
- int amColor = mAmPmUnselectedColor;
- int amAlpha = ALPHA_OPAQUE;
- int pmColor = mAmPmUnselectedColor;
- int pmAlpha = ALPHA_OPAQUE;
- if (mAmOrPm == AM) {
- amColor = mAmPmSelectedColor;
- amAlpha = ALPHA_AMPM_SELECTED;
- } else if (mAmOrPm == PM) {
- pmColor = mAmPmSelectedColor;
- pmAlpha = ALPHA_AMPM_SELECTED;
- }
- if (mAmOrPmPressed == AM) {
- amColor = mAmPmSelectedColor;
- amAlpha = ALPHA_AMPM_PRESSED;
- } else if (mAmOrPmPressed == PM) {
- pmColor = mAmPmSelectedColor;
- pmAlpha = ALPHA_AMPM_PRESSED;
- }
-
- // Draw the two circles
- mPaintAmPmCircle[AM].setColor(amColor);
- mPaintAmPmCircle[AM].setAlpha(getMultipliedAlpha(amColor, amAlpha));
- canvas.drawCircle(isLayoutRtl ? mRightIndicatorXCenter : mLeftIndicatorXCenter,
- mAmPmYCenter, mAmPmCircleRadius, mPaintAmPmCircle[AM]);
-
- mPaintAmPmCircle[PM].setColor(pmColor);
- mPaintAmPmCircle[PM].setAlpha(getMultipliedAlpha(pmColor, pmAlpha));
- canvas.drawCircle(isLayoutRtl ? mLeftIndicatorXCenter : mRightIndicatorXCenter,
- mAmPmYCenter, mAmPmCircleRadius, mPaintAmPmCircle[PM]);
-
- // Draw the AM/PM texts on top
- mPaintAmPmText.setColor(mAmPmTextColor);
- float textYCenter = mAmPmYCenter -
- (int) (mPaintAmPmText.descent() + mPaintAmPmText.ascent()) / 2;
-
- canvas.drawText(isLayoutRtl ? mAmPmText[PM] : mAmPmText[AM], mLeftIndicatorXCenter,
- textYCenter, mPaintAmPmText);
- canvas.drawText(isLayoutRtl ? mAmPmText[AM] : mAmPmText[PM], mRightIndicatorXCenter,
- textYCenter, mPaintAmPmText);
- }
-
private int getMultipliedAlpha(int argb, int alpha) {
return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5);
}
@@ -917,20 +855,17 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
float top = mYCenter - outerRadius;
float right = mXCenter + outerRadius;
float bottom = mYCenter + outerRadius;
- mRectF = new RectF(left, top, right, bottom);
- canvas.drawRect(mRectF, mPaintDebug);
+ canvas.drawRect(left, top, right, bottom, mPaintDebug);
// Draw outer rectangle for background
left = mXCenter - mCircleRadius[HOURS];
top = mYCenter - mCircleRadius[HOURS];
right = mXCenter + mCircleRadius[HOURS];
bottom = mYCenter + mCircleRadius[HOURS];
- mRectF.set(left, top, right, bottom);
- canvas.drawRect(mRectF, mPaintDebug);
+ canvas.drawRect(left, top, right, bottom, mPaintDebug);
// Draw outer view rectangle
- mRectF.set(0, 0, getWidth(), getHeight());
- canvas.drawRect(mRectF, mPaintDebug);
+ canvas.drawRect(0, 0, getWidth(), getHeight(), mPaintDebug);
// Draw selected time
final String selected = String.format("%02d:%02d", getCurrentHour(), getCurrentMinute());
@@ -950,7 +885,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
float x = mXCenter - width / 2;
float y = mYCenter + 1.5f * height;
- canvas.drawText(selected.toString(), x, y, paint);
+ canvas.drawText(selected, x, y, paint);
}
private void calculateGridSizesHours() {
@@ -1044,12 +979,14 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
}
// Used for animating the hours by changing their radius
+ @SuppressWarnings("unused")
private void setAnimationRadiusMultiplierHours(float animationRadiusMultiplier) {
mAnimationRadiusMultiplier[HOURS] = animationRadiusMultiplier;
mAnimationRadiusMultiplier[HOURS_INNER] = animationRadiusMultiplier;
}
// Used for animating the minutes by changing their radius
+ @SuppressWarnings("unused")
private void setAnimationRadiusMultiplierMinutes(float animationRadiusMultiplier) {
mAnimationRadiusMultiplier[MINUTES] = animationRadiusMultiplier;
}
@@ -1242,234 +1179,428 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
}
final float opposite = Math.abs(y - mYCenter);
- double degrees = Math.toDegrees(Math.asin(opposite / hypotenuse));
+ int degrees = (int) (Math.toDegrees(Math.asin(opposite / hypotenuse)) + 0.5);
// Now we have to translate to the correct quadrant.
- boolean rightSide = (x > mXCenter);
- boolean topSide = (y < mYCenter);
- if (rightSide && topSide) {
- degrees = 90 - degrees;
- } else if (rightSide && !topSide) {
- degrees = 90 + degrees;
- } else if (!rightSide && !topSide) {
- degrees = 270 - degrees;
- } else if (!rightSide && topSide) {
- degrees = 270 + degrees;
+ final boolean rightSide = (x > mXCenter);
+ final boolean topSide = (y < mYCenter);
+ if (rightSide) {
+ if (topSide) {
+ degrees = 90 - degrees;
+ } else {
+ degrees = 90 + degrees;
+ }
+ } else {
+ if (topSide) {
+ degrees = 270 + degrees;
+ } else {
+ degrees = 270 - degrees;
+ }
}
- return (int) degrees;
+ return degrees;
}
- private int getIsTouchingAmOrPm(float x, float y) {
- final boolean isLayoutRtl = isLayoutRtl();
- int squaredYDistance = (int) ((y - mAmPmYCenter) * (y - mAmPmYCenter));
+ boolean mChangedDuringTouch = false;
- int distanceToAmCenter = (int) Math.sqrt(
- (x - mLeftIndicatorXCenter) * (x - mLeftIndicatorXCenter) + squaredYDistance);
- if (distanceToAmCenter <= mAmPmCircleRadius) {
- return (isLayoutRtl ? PM : AM);
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (!mInputEnabled) {
+ return true;
}
- int distanceToPmCenter = (int) Math.sqrt(
- (x - mRightIndicatorXCenter) * (x - mRightIndicatorXCenter) + squaredYDistance);
- if (distanceToPmCenter <= mAmPmCircleRadius) {
- return (isLayoutRtl ? AM : PM);
+ final int action = event.getActionMasked();
+ if (action == MotionEvent.ACTION_MOVE
+ || action == MotionEvent.ACTION_UP
+ || action == MotionEvent.ACTION_DOWN) {
+ boolean forceSelection = false;
+ boolean autoAdvance = false;
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ // This is a new event stream, reset whether the value changed.
+ mChangedDuringTouch = false;
+ } else if (action == MotionEvent.ACTION_UP) {
+ autoAdvance = true;
+
+ // If we saw a down/up pair without the value changing, assume
+ // this is a single-tap selection and force a change.
+ if (!mChangedDuringTouch) {
+ forceSelection = true;
+ }
+ }
+
+ mChangedDuringTouch |= handleTouchInput(
+ event.getX(), event.getY(), forceSelection, autoAdvance);
}
- // Neither was close enough.
- return -1;
+ return true;
}
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if(!mInputEnabled) {
- return true;
+ private boolean handleTouchInput(
+ float x, float y, boolean forceSelection, boolean autoAdvance) {
+ // Calling getDegreesFromXY has side effects, so cache
+ // whether we used to be on the inner circle.
+ final boolean wasOnInnerCircle = mIsOnInnerCircle;
+ final int degrees = getDegreesFromXY(x, y);
+ if (degrees == -1) {
+ return false;
}
- final float eventX = event.getX();
- final float eventY = event.getY();
+ final int[] selectionDegrees = mSelectionDegrees;
+ final int type;
+ final int newValue;
+ final boolean valueChanged;
- int degrees;
- int snapDegrees;
- boolean result = false;
+ if (mShowHours) {
+ final int snapDegrees = snapOnly30s(degrees, 0) % 360;
+ valueChanged = selectionDegrees[HOURS] != snapDegrees
+ || selectionDegrees[HOURS_INNER] != snapDegrees
+ || wasOnInnerCircle != mIsOnInnerCircle;
+
+ selectionDegrees[HOURS] = snapDegrees;
+ selectionDegrees[HOURS_INNER] = snapDegrees;
+ type = HOURS;
+ newValue = getCurrentHour();
+ } else {
+ final int snapDegrees = snapPrefer30s(degrees) % 360;
+ valueChanged = selectionDegrees[MINUTES] != snapDegrees;
- switch(event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_MOVE:
- mAmOrPmPressed = getIsTouchingAmOrPm(eventX, eventY);
- if (mAmOrPmPressed != -1) {
- result = true;
- } else {
- degrees = getDegreesFromXY(eventX, eventY);
- if (degrees != -1) {
- snapDegrees = (mShowHours ?
- snapOnly30s(degrees, 0) : snapPrefer30s(degrees)) % 360;
- if (mShowHours) {
- mSelectionDegrees[HOURS] = snapDegrees;
- mSelectionDegrees[HOURS_INNER] = snapDegrees;
- } else {
- mSelectionDegrees[MINUTES] = snapDegrees;
- }
- performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
- if (mListener != null) {
- if (mShowHours) {
- mListener.onValueSelected(HOURS, getCurrentHour(), false);
- } else {
- mListener.onValueSelected(MINUTES, getCurrentMinute(), false);
- }
- }
- result = true;
- }
- }
- invalidate();
- return result;
+ selectionDegrees[MINUTES] = snapDegrees;
+ type = MINUTES;
+ newValue = getCurrentMinute();
+ }
- case MotionEvent.ACTION_UP:
- mAmOrPmPressed = getIsTouchingAmOrPm(eventX, eventY);
- if (mAmOrPmPressed != -1) {
- if (mAmOrPm != mAmOrPmPressed) {
- swapAmPm();
- }
- mAmOrPmPressed = -1;
- if (mListener != null) {
- mListener.onValueSelected(AMPM, getCurrentHour(), true);
- }
- result = true;
- } else {
- degrees = getDegreesFromXY(eventX, eventY);
- if (degrees != -1) {
- snapDegrees = (mShowHours ?
- snapOnly30s(degrees, 0) : snapPrefer30s(degrees)) % 360;
- if (mShowHours) {
- mSelectionDegrees[HOURS] = snapDegrees;
- mSelectionDegrees[HOURS_INNER] = snapDegrees;
- } else {
- mSelectionDegrees[MINUTES] = snapDegrees;
- }
- if (mListener != null) {
- if (mShowHours) {
- mListener.onValueSelected(HOURS, getCurrentHour(), true);
- } else {
- mListener.onValueSelected(MINUTES, getCurrentMinute(), true);
- }
- }
- result = true;
- }
- }
- if (result) {
- invalidate();
- }
- return result;
+ if (valueChanged || forceSelection || autoAdvance) {
+ // Fire the listener even if we just need to auto-advance.
+ if (mListener != null) {
+ mListener.onValueSelected(type, newValue, autoAdvance);
+ }
- default:
- break;
+ // Only provide feedback if the value actually changed.
+ if (valueChanged || forceSelection) {
+ performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
+ invalidate();
+ }
+ return true;
}
- return false;
- }
- /**
- * Necessary for accessibility, to ensure we support "scrolling" forward and backward
- * in the circle.
- */
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
- info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ return false;
}
- /**
- * Announce the currently-selected time when launched.
- */
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
- // Clear the event's current text so that only the current time will be spoken.
- event.getText().clear();
- Time time = new Time();
- time.hour = getCurrentHour();
- time.minute = getCurrentMinute();
- long millis = time.normalize(true);
- int flags = DateUtils.FORMAT_SHOW_TIME;
- if (mIs24HourMode) {
- flags |= DateUtils.FORMAT_24HOUR;
- }
- String timeString = DateUtils.formatDateTime(getContext(), millis, flags);
- event.getText().add(timeString);
+ public boolean dispatchHoverEvent(MotionEvent event) {
+ // First right-of-refusal goes the touch exploration helper.
+ if (mTouchHelper.dispatchHoverEvent(event)) {
return true;
}
- return super.dispatchPopulateAccessibilityEvent(event);
+ return super.dispatchHoverEvent(event);
}
- /**
- * When scroll forward/backward events are received, jump the time to the higher/lower
- * discrete, visible value on the circle.
- */
- @SuppressLint("NewApi")
- @Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (super.performAccessibilityAction(action, arguments)) {
- return true;
+ public void setInputEnabled(boolean inputEnabled) {
+ mInputEnabled = inputEnabled;
+ invalidate();
+ }
+
+ private class RadialPickerTouchHelper extends ExploreByTouchHelper {
+ private final Rect mTempRect = new Rect();
+
+ private final int TYPE_HOUR = 1;
+ private final int TYPE_MINUTE = 2;
+
+ private final int SHIFT_TYPE = 0;
+ private final int MASK_TYPE = 0xF;
+
+ private final int SHIFT_VALUE = 8;
+ private final int MASK_VALUE = 0xFF;
+
+ /** Increment in which virtual views are exposed for minutes. */
+ private final int MINUTE_INCREMENT = 5;
+
+ public RadialPickerTouchHelper() {
+ super(RadialTimePickerView.this);
}
- int changeMultiplier = 0;
- if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) {
- changeMultiplier = 1;
- } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
- changeMultiplier = -1;
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
}
- if (changeMultiplier != 0) {
- int value = 0;
- int stepSize = 0;
- if (mShowHours) {
- stepSize = DEGREES_FOR_ONE_HOUR;
- value = getCurrentHour() % 12;
- } else {
- stepSize = DEGREES_FOR_ONE_MINUTE;
- value = getCurrentMinute();
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
+ if (super.performAccessibilityAction(host, action, arguments)) {
+ return true;
}
- int degrees = value * stepSize;
- degrees = snapOnly30s(degrees, changeMultiplier);
- value = degrees / stepSize;
- int maxValue = 0;
- int minValue = 0;
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
+ adjustPicker(1);
+ return true;
+ case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
+ adjustPicker(-1);
+ return true;
+ }
+
+ return false;
+ }
+
+ private void adjustPicker(int step) {
+ final int stepSize;
+ final int initialValue;
+ final int maxValue;
+ final int minValue;
if (mShowHours) {
+ stepSize = DEGREES_FOR_ONE_HOUR;
+ initialValue = getCurrentHour() % 12;
+
if (mIs24HourMode) {
maxValue = 23;
+ minValue = 0;
} else {
maxValue = 12;
minValue = 1;
}
} else {
+ stepSize = DEGREES_FOR_ONE_MINUTE;
+ initialValue = getCurrentMinute();
+
maxValue = 55;
+ minValue = 0;
+ }
+
+ final int steppedValue = snapOnly30s(initialValue * stepSize, step) / stepSize;
+ final int clampedValue = MathUtils.constrain(steppedValue, minValue, maxValue);
+ if (mShowHours) {
+ setCurrentHour(clampedValue);
+ } else {
+ setCurrentMinute(clampedValue);
}
- if (value > maxValue) {
- // If we scrolled forward past the highest number, wrap around to the lowest.
- value = minValue;
- } else if (value < minValue) {
- // If we scrolled backward past the lowest number, wrap around to the highest.
- value = maxValue;
+ }
+
+ @Override
+ protected int getVirtualViewAt(float x, float y) {
+ final int id;
+
+ // Calling getDegreesXY() has side-effects, so we need to cache the
+ // current inner circle value and restore after the call.
+ final boolean wasOnInnerCircle = mIsOnInnerCircle;
+ final int degrees = getDegreesFromXY(x, y);
+ final boolean isOnInnerCircle = mIsOnInnerCircle;
+ mIsOnInnerCircle = wasOnInnerCircle;
+
+ if (degrees != -1) {
+ final int snapDegrees = snapOnly30s(degrees, 0) % 360;
+ if (mShowHours) {
+ final int hour24 = getHourForDegrees(snapDegrees, isOnInnerCircle);
+ final int hour = mIs24HourMode ? hour24 : hour24To12(hour24);
+ id = makeId(TYPE_HOUR, hour);
+ } else {
+ final int current = getCurrentMinute();
+ final int touched = getMinuteForDegrees(degrees);
+ final int snapped = getMinuteForDegrees(snapDegrees);
+
+ // If the touched minute is closer to the current minute
+ // than it is to the snapped minute, return current.
+ final int minute;
+ if (Math.abs(current - touched) < Math.abs(snapped - touched)) {
+ minute = current;
+ } else {
+ minute = snapped;
+ }
+ id = makeId(TYPE_MINUTE, minute);
+ }
+ } else {
+ id = INVALID_ID;
}
+
+ return id;
+ }
+
+ @Override
+ protected void getVisibleVirtualViews(IntArray virtualViewIds) {
if (mShowHours) {
- setCurrentHour(value);
- if (mListener != null) {
- mListener.onValueSelected(HOURS, value, false);
+ final int min = mIs24HourMode ? 0 : 1;
+ final int max = mIs24HourMode ? 23 : 12;
+ for (int i = min; i <= max ; i++) {
+ virtualViewIds.add(makeId(TYPE_HOUR, i));
}
} else {
- setCurrentMinute(value);
- if (mListener != null) {
- mListener.onValueSelected(MINUTES, value, false);
+ final int current = getCurrentMinute();
+ for (int i = 0; i < 60; i += MINUTE_INCREMENT) {
+ virtualViewIds.add(makeId(TYPE_MINUTE, i));
+
+ // If the current minute falls between two increments,
+ // insert an extra node for it.
+ if (current > i && current < i + MINUTE_INCREMENT) {
+ virtualViewIds.add(makeId(TYPE_MINUTE, current));
+ }
}
}
- return true;
}
- return false;
- }
+ @Override
+ protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+ event.setClassName(getClass().getName());
- public void setInputEnabled(boolean inputEnabled) {
- mInputEnabled = inputEnabled;
- invalidate();
+ final int type = getTypeFromId(virtualViewId);
+ final int value = getValueFromId(virtualViewId);
+ final CharSequence description = getVirtualViewDescription(type, value);
+ event.setContentDescription(description);
+ }
+
+ @Override
+ protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
+ node.setClassName(getClass().getName());
+ node.addAction(AccessibilityAction.ACTION_CLICK);
+
+ final int type = getTypeFromId(virtualViewId);
+ final int value = getValueFromId(virtualViewId);
+ final CharSequence description = getVirtualViewDescription(type, value);
+ node.setContentDescription(description);
+
+ getBoundsForVirtualView(virtualViewId, mTempRect);
+ node.setBoundsInParent(mTempRect);
+
+ final boolean selected = isVirtualViewSelected(type, value);
+ node.setSelected(selected);
+
+ final int nextId = getVirtualViewIdAfter(type, value);
+ if (nextId != INVALID_ID) {
+ node.setTraversalBefore(RadialTimePickerView.this, nextId);
+ }
+ }
+
+ private int getVirtualViewIdAfter(int type, int value) {
+ if (type == TYPE_HOUR) {
+ final int nextValue = value + 1;
+ final int max = mIs24HourMode ? 23 : 12;
+ if (nextValue <= max) {
+ return makeId(type, nextValue);
+ }
+ } else if (type == TYPE_MINUTE) {
+ final int current = getCurrentMinute();
+ final int snapValue = value - (value % MINUTE_INCREMENT);
+ final int nextValue = snapValue + MINUTE_INCREMENT;
+ if (value < current && nextValue > current) {
+ // The current value is between two snap values.
+ return makeId(type, current);
+ } else if (nextValue < 60) {
+ return makeId(type, nextValue);
+ }
+ }
+ return INVALID_ID;
+ }
+
+ @Override
+ protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
+ Bundle arguments) {
+ if (action == AccessibilityNodeInfo.ACTION_CLICK) {
+ final int type = getTypeFromId(virtualViewId);
+ final int value = getValueFromId(virtualViewId);
+ if (type == TYPE_HOUR) {
+ final int hour = mIs24HourMode ? value : hour12To24(value, mAmOrPm);
+ setCurrentHour(hour);
+ return true;
+ } else if (type == TYPE_MINUTE) {
+ setCurrentMinute(value);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int hour12To24(int hour12, int amOrPm) {
+ int hour24 = hour12;
+ if (hour12 == 12) {
+ if (amOrPm == AM) {
+ hour24 = 0;
+ }
+ } else if (amOrPm == PM) {
+ hour24 += 12;
+ }
+ return hour24;
+ }
+
+ private int hour24To12(int hour24) {
+ if (hour24 == 0) {
+ return 12;
+ } else if (hour24 > 12) {
+ return hour24 - 12;
+ } else {
+ return hour24;
+ }
+ }
+
+ private void getBoundsForVirtualView(int virtualViewId, Rect bounds) {
+ final float radius;
+ final int type = getTypeFromId(virtualViewId);
+ final int value = getValueFromId(virtualViewId);
+ final float centerRadius;
+ final float degrees;
+ if (type == TYPE_HOUR) {
+ final boolean innerCircle = mIs24HourMode && value > 0 && value <= 12;
+ if (innerCircle) {
+ centerRadius = mCircleRadius[HOURS_INNER] * mNumbersRadiusMultiplier[HOURS_INNER];
+ radius = mSelectionRadius[HOURS_INNER];
+ } else {
+ centerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS];
+ radius = mSelectionRadius[HOURS];
+ }
+
+ degrees = getDegreesForHour(value);
+ } else if (type == TYPE_MINUTE) {
+ centerRadius = mCircleRadius[MINUTES] * mNumbersRadiusMultiplier[MINUTES];
+ degrees = getDegreesForMinute(value);
+ radius = mSelectionRadius[MINUTES];
+ } else {
+ // This should never happen.
+ centerRadius = 0;
+ degrees = 0;
+ radius = 0;
+ }
+
+ final double radians = Math.toRadians(degrees);
+ final float xCenter = mXCenter + centerRadius * (float) Math.sin(radians);
+ final float yCenter = mYCenter - centerRadius * (float) Math.cos(radians);
+
+ bounds.set((int) (xCenter - radius), (int) (yCenter - radius),
+ (int) (xCenter + radius), (int) (yCenter + radius));
+ }
+
+ private CharSequence getVirtualViewDescription(int type, int value) {
+ final CharSequence description;
+ if (type == TYPE_HOUR || type == TYPE_MINUTE) {
+ description = Integer.toString(value);
+ } else {
+ description = null;
+ }
+ return description;
+ }
+
+ private boolean isVirtualViewSelected(int type, int value) {
+ final boolean selected;
+ if (type == TYPE_HOUR) {
+ selected = getCurrentHour() == value;
+ } else if (type == TYPE_MINUTE) {
+ selected = getCurrentMinute() == value;
+ } else {
+ selected = false;
+ }
+ return selected;
+ }
+
+ private int makeId(int type, int value) {
+ return type << SHIFT_TYPE | value << SHIFT_VALUE;
+ }
+
+ private int getTypeFromId(int id) {
+ return id >>> SHIFT_TYPE & MASK_TYPE;
+ }
+
+ private int getValueFromId(int id) {
+ return id >>> SHIFT_VALUE & MASK_VALUE;
+ }
}
private static class IntHolder {
diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java
index c4a7c0a..82b490e 100644
--- a/core/java/android/widget/RatingBar.java
+++ b/core/java/android/widget/RatingBar.java
@@ -314,10 +314,6 @@ public class RatingBar extends AbsSeekBar {
dispatchRatingChange(true);
}
- @Override
- void animateSetProgress(int progress) {
- }
-
void dispatchRatingChange(boolean fromUser) {
if (mOnRatingBarChangeListener != null) {
mOnRatingBarChangeListener.onRatingChanged(this, getRating(),
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7cb3c37..dd7fa18 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -27,6 +27,7 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -1069,6 +1070,7 @@ public class RemoteViews implements Parcelable, Filter {
static final int BITMAP = 12;
static final int BUNDLE = 13;
static final int INTENT = 14;
+ static final int COLOR_STATE_LIST = 15;
String methodName;
int type;
@@ -1142,6 +1144,11 @@ public class RemoteViews implements Parcelable, Filter {
this.value = Intent.CREATOR.createFromParcel(in);
}
break;
+ case COLOR_STATE_LIST:
+ if (in.readInt() != 0) {
+ this.value = ColorStateList.CREATOR.createFromParcel(in);
+ }
+ break;
default:
break;
}
@@ -1212,6 +1219,11 @@ public class RemoteViews implements Parcelable, Filter {
((Intent)this.value).writeToParcel(out, flags);
}
break;
+ case COLOR_STATE_LIST:
+ out.writeInt(this.value != null ? 1 : 0);
+ if (this.value != null) {
+ ((ColorStateList)this.value).writeToParcel(out, flags);
+ }
default:
break;
}
@@ -1247,6 +1259,8 @@ public class RemoteViews implements Parcelable, Filter {
return Bundle.class;
case INTENT:
return Intent.class;
+ case COLOR_STATE_LIST:
+ return ColorStateList.class;
default:
return null;
}
@@ -2207,6 +2221,42 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * @hide
+ * Equivalent to calling {@link android.widget.ProgressBar#setProgressTintList}.
+ *
+ * @param viewId The id of the view whose tint should change
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ */
+ public void setProgressTintList(int viewId, ColorStateList tint) {
+ addAction(new ReflectionAction(viewId, "setProgressTintList",
+ ReflectionAction.COLOR_STATE_LIST, tint));
+ }
+
+ /**
+ * @hide
+ * Equivalent to calling {@link android.widget.ProgressBar#setProgressBackgroundTintList}.
+ *
+ * @param viewId The id of the view whose tint should change
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ */
+ public void setProgressBackgroundTintList(int viewId, ColorStateList tint) {
+ addAction(new ReflectionAction(viewId, "setProgressBackgroundTintList",
+ ReflectionAction.COLOR_STATE_LIST, tint));
+ }
+
+ /**
+ * @hide
+ * Equivalent to calling {@link android.widget.ProgressBar#setIndeterminateTintList}.
+ *
+ * @param viewId The id of the view whose tint should change
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ */
+ public void setProgressIndeterminateTintList(int viewId, ColorStateList tint) {
+ addAction(new ReflectionAction(viewId, "setIndeterminateTintList",
+ ReflectionAction.COLOR_STATE_LIST, tint));
+ }
+
+ /**
* Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
*
* @param viewId The id of the view whose text color should change
@@ -2478,6 +2528,26 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * Equivalent to calling {@link android.view.View#setAccessibilityTraversalBefore(int)}.
+ *
+ * @param viewId The id of the view whose before view in accessibility traversal to set.
+ * @param nextId The id of the next in the accessibility traversal.
+ **/
+ public void setAccessibilityTraversalBefore(int viewId, int nextId) {
+ setInt(viewId, "setAccessibilityTraversalBefore", nextId);
+ }
+
+ /**
+ * Equivalent to calling {@link android.view.View#setAccessibilityTraversalAfter(int)}.
+ *
+ * @param viewId The id of the view whose after view in accessibility traversal to set.
+ * @param nextId The id of the next in the accessibility traversal.
+ **/
+ public void setAccessibilityTraversalAfter(int viewId, int nextId) {
+ setInt(viewId, "setAccessibilityTraversalAfter", nextId);
+ }
+
+ /**
* Equivalent to calling View.setLabelFor(int).
*
* @param viewId The id of the view whose property to set.
diff --git a/core/java/android/widget/ScrollBarDrawable.java b/core/java/android/widget/ScrollBarDrawable.java
index 937457c..8eff1aa 100644
--- a/core/java/android/widget/ScrollBarDrawable.java
+++ b/core/java/android/widget/ScrollBarDrawable.java
@@ -29,6 +29,8 @@ import android.graphics.drawable.Drawable;
* {@hide}
*/
public class ScrollBarDrawable extends Drawable {
+ private static final int[] STATE_ENABLED = new int[] { android.R.attr.state_enabled };
+
private Drawable mVerticalTrack;
private Drawable mHorizontalTrack;
private Drawable mVerticalThumb;
@@ -42,6 +44,7 @@ public class ScrollBarDrawable extends Drawable {
private final Rect mTempBounds = new Rect();
private boolean mAlwaysDrawHorizontalTrack;
private boolean mAlwaysDrawVerticalTrack;
+ private boolean mMutated;
public ScrollBarDrawable() {
}
@@ -189,21 +192,41 @@ public class ScrollBarDrawable extends Drawable {
public void setVerticalThumbDrawable(Drawable thumb) {
if (thumb != null) {
+ if (mMutated) {
+ thumb.mutate();
+ }
+ thumb.setState(STATE_ENABLED);
mVerticalThumb = thumb;
}
}
public void setVerticalTrackDrawable(Drawable track) {
+ if (track != null) {
+ if (mMutated) {
+ track.mutate();
+ }
+ track.setState(STATE_ENABLED);
+ }
mVerticalTrack = track;
}
public void setHorizontalThumbDrawable(Drawable thumb) {
if (thumb != null) {
+ if (mMutated) {
+ thumb.mutate();
+ }
+ thumb.setState(STATE_ENABLED);
mHorizontalThumb = thumb;
}
}
public void setHorizontalTrackDrawable(Drawable track) {
+ if (track != null) {
+ if (mMutated) {
+ track.mutate();
+ }
+ track.setState(STATE_ENABLED);
+ }
mHorizontalTrack = track;
}
@@ -218,6 +241,26 @@ public class ScrollBarDrawable extends Drawable {
}
@Override
+ public ScrollBarDrawable mutate() {
+ if (!mMutated && super.mutate() == this) {
+ if (mVerticalTrack != null) {
+ mVerticalTrack.mutate();
+ }
+ if (mVerticalThumb != null) {
+ mVerticalThumb.mutate();
+ }
+ if (mHorizontalTrack != null) {
+ mHorizontalTrack.mutate();
+ }
+ if (mHorizontalThumb != null) {
+ mHorizontalThumb.mutate();
+ }
+ mMutated = true;
+ }
+ return this;
+ }
+
+ @Override
public void setAlpha(int alpha) {
if (mVerticalTrack != null) {
mVerticalTrack.setAlpha(alpha);
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index dfdf606..4ee6418 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -45,7 +45,6 @@ import android.text.TextWatcher;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.TypedValue;
import android.view.CollapsibleActionView;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -99,17 +98,21 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
*/
private static final String IME_OPTION_NO_MICROPHONE = "nm";
- private final SearchAutoComplete mQueryTextView;
+ private final SearchAutoComplete mSearchSrcTextView;
private final View mSearchEditFrame;
private final View mSearchPlate;
private final View mSubmitArea;
private final ImageView mSearchButton;
- private final ImageView mSubmitButton;
+ private final ImageView mGoButton;
private final ImageView mCloseButton;
private final ImageView mVoiceButton;
- private final ImageView mSearchHintIcon;
private final View mDropDownAnchor;
- private final int mSearchIconResId;
+
+ /** Icon optionally displayed when the SearchView is collapsed. */
+ private final ImageView mCollapsedIcon;
+
+ /** Drawable used as an EditText hint. */
+ private final Drawable mSearchHintIcon;
// Resources used by SuggestionsAdapter to display suggestions.
private final int mSuggestionRowLayout;
@@ -262,30 +265,38 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
attrs, R.styleable.SearchView, defStyleAttr, defStyleRes);
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
- final int layoutResId = a.getResourceId(R.styleable.SearchView_layout, R.layout.search_view);
+ final int layoutResId = a.getResourceId(
+ R.styleable.SearchView_layout, R.layout.search_view);
inflater.inflate(layoutResId, this, true);
- mQueryTextView = (SearchAutoComplete) findViewById(R.id.search_src_text);
- mQueryTextView.setSearchView(this);
+ mSearchSrcTextView = (SearchAutoComplete) findViewById(R.id.search_src_text);
+ mSearchSrcTextView.setSearchView(this);
mSearchEditFrame = findViewById(R.id.search_edit_frame);
mSearchPlate = findViewById(R.id.search_plate);
mSubmitArea = findViewById(R.id.submit_area);
mSearchButton = (ImageView) findViewById(R.id.search_button);
- mSubmitButton = (ImageView) findViewById(R.id.search_go_btn);
+ mGoButton = (ImageView) findViewById(R.id.search_go_btn);
mCloseButton = (ImageView) findViewById(R.id.search_close_btn);
mVoiceButton = (ImageView) findViewById(R.id.search_voice_btn);
- mSearchHintIcon = (ImageView) findViewById(R.id.search_mag_icon);
+ mCollapsedIcon = (ImageView) findViewById(R.id.search_mag_icon);
// Set up icons and backgrounds.
mSearchPlate.setBackground(a.getDrawable(R.styleable.SearchView_queryBackground));
mSubmitArea.setBackground(a.getDrawable(R.styleable.SearchView_submitBackground));
- mSearchIconResId = a.getResourceId(R.styleable.SearchView_searchIcon, 0);
- mSearchButton.setImageResource(mSearchIconResId);
- mSubmitButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_goIcon));
+ mSearchButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_searchIcon));
+ mGoButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_goIcon));
mCloseButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_closeIcon));
mVoiceButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_voiceIcon));
- mSearchHintIcon.setImageDrawable(a.getDrawable(R.styleable.SearchView_searchIcon));
+ mCollapsedIcon.setImageDrawable(a.getDrawable(R.styleable.SearchView_searchIcon));
+
+ // Prior to L MR1, the search hint icon defaulted to searchIcon. If the
+ // style does not have an explicit value set, fall back to that.
+ if (a.hasValueOrEmpty(R.styleable.SearchView_searchHintIcon)) {
+ mSearchHintIcon = a.getDrawable(R.styleable.SearchView_searchHintIcon);
+ } else {
+ mSearchHintIcon = a.getDrawable(R.styleable.SearchView_searchIcon);
+ }
// Extract dropdown layout resource IDs for later use.
mSuggestionRowLayout = a.getResourceId(R.styleable.SearchView_suggestionRowLayout,
@@ -294,18 +305,18 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
mSearchButton.setOnClickListener(mOnClickListener);
mCloseButton.setOnClickListener(mOnClickListener);
- mSubmitButton.setOnClickListener(mOnClickListener);
+ mGoButton.setOnClickListener(mOnClickListener);
mVoiceButton.setOnClickListener(mOnClickListener);
- mQueryTextView.setOnClickListener(mOnClickListener);
+ mSearchSrcTextView.setOnClickListener(mOnClickListener);
- mQueryTextView.addTextChangedListener(mTextWatcher);
- mQueryTextView.setOnEditorActionListener(mOnEditorActionListener);
- mQueryTextView.setOnItemClickListener(mOnItemClickListener);
- mQueryTextView.setOnItemSelectedListener(mOnItemSelectedListener);
- mQueryTextView.setOnKeyListener(mTextKeyListener);
+ mSearchSrcTextView.addTextChangedListener(mTextWatcher);
+ mSearchSrcTextView.setOnEditorActionListener(mOnEditorActionListener);
+ mSearchSrcTextView.setOnItemClickListener(mOnItemClickListener);
+ mSearchSrcTextView.setOnItemSelectedListener(mOnItemSelectedListener);
+ mSearchSrcTextView.setOnKeyListener(mTextKeyListener);
// Inform any listener of focus changes
- mQueryTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
+ mSearchSrcTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
if (mOnQueryTextFocusChangeListener != null) {
@@ -350,7 +361,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mDropDownAnchor = findViewById(mQueryTextView.getDropDownAnchor());
+ mDropDownAnchor = findViewById(mSearchSrcTextView.getDropDownAnchor());
if (mDropDownAnchor != null) {
mDropDownAnchor.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
@@ -393,7 +404,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
if (mVoiceButtonEnabled) {
// Disable the microphone on the keyboard, as a mic is displayed near the text box
// TODO: use imeOptions to disable voice input when the new API will be available
- mQueryTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
+ mSearchSrcTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
}
updateViewsVisibility(isIconified());
}
@@ -416,7 +427,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
* @attr ref android.R.styleable#SearchView_imeOptions
*/
public void setImeOptions(int imeOptions) {
- mQueryTextView.setImeOptions(imeOptions);
+ mSearchSrcTextView.setImeOptions(imeOptions);
}
/**
@@ -427,7 +438,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
* @attr ref android.R.styleable#SearchView_imeOptions
*/
public int getImeOptions() {
- return mQueryTextView.getImeOptions();
+ return mSearchSrcTextView.getImeOptions();
}
/**
@@ -439,7 +450,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
* @attr ref android.R.styleable#SearchView_inputType
*/
public void setInputType(int inputType) {
- mQueryTextView.setInputType(inputType);
+ mSearchSrcTextView.setInputType(inputType);
}
/**
@@ -449,7 +460,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
* @attr ref android.R.styleable#SearchView_inputType
*/
public int getInputType() {
- return mQueryTextView.getInputType();
+ return mSearchSrcTextView.getInputType();
}
/** @hide */
@@ -461,7 +472,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
if (!isFocusable()) return false;
// If it is not iconified, then give the focus to the text field
if (!isIconified()) {
- boolean result = mQueryTextView.requestFocus(direction, previouslyFocusedRect);
+ boolean result = mSearchSrcTextView.requestFocus(direction, previouslyFocusedRect);
if (result) {
updateViewsVisibility(false);
}
@@ -477,7 +488,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
mClearingFocus = true;
setImeVisibility(false);
super.clearFocus();
- mQueryTextView.clearFocus();
+ mSearchSrcTextView.clearFocus();
mClearingFocus = false;
}
@@ -536,7 +547,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
* @return the query string
*/
public CharSequence getQuery() {
- return mQueryTextView.getText();
+ return mSearchSrcTextView.getText();
}
/**
@@ -548,9 +559,9 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
* text field.
*/
public void setQuery(CharSequence query, boolean submit) {
- mQueryTextView.setText(query);
+ mSearchSrcTextView.setText(query);
if (query != null) {
- mQueryTextView.setSelection(mQueryTextView.length());
+ mSearchSrcTextView.setSelection(mSearchSrcTextView.length());
mUserQuery = query;
}
@@ -711,7 +722,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
public void setSuggestionsAdapter(CursorAdapter adapter) {
mSuggestionsAdapter = adapter;
- mQueryTextView.setAdapter(mSuggestionsAdapter);
+ mSearchSrcTextView.setAdapter(mSuggestionsAdapter);
}
/**
@@ -789,12 +800,12 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
// Visibility of views that are visible when collapsed
final int visCollapsed = collapsed ? VISIBLE : GONE;
// Is there text in the query
- final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText());
+ final boolean hasText = !TextUtils.isEmpty(mSearchSrcTextView.getText());
mSearchButton.setVisibility(visCollapsed);
updateSubmitButton(hasText);
mSearchEditFrame.setVisibility(collapsed ? GONE : VISIBLE);
- mSearchHintIcon.setVisibility(mIconifiedByDefault ? GONE : VISIBLE);
+ mCollapsedIcon.setVisibility(mIconifiedByDefault ? GONE : VISIBLE);
updateCloseButton();
updateVoiceButton(!hasText);
updateSubmitArea();
@@ -827,13 +838,13 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
&& (hasText || !mVoiceButtonEnabled)) {
visibility = VISIBLE;
}
- mSubmitButton.setVisibility(visibility);
+ mGoButton.setVisibility(visibility);
}
private void updateSubmitArea() {
int visibility = GONE;
if (isSubmitAreaEnabled()
- && (mSubmitButton.getVisibility() == VISIBLE
+ && (mGoButton.getVisibility() == VISIBLE
|| mVoiceButton.getVisibility() == VISIBLE)) {
visibility = VISIBLE;
}
@@ -841,12 +852,15 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
}
private void updateCloseButton() {
- final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText());
+ final boolean hasText = !TextUtils.isEmpty(mSearchSrcTextView.getText());
// Should we show the close button? It is not shown if there's no focus,
// field is not iconified by default and there is no text in it.
final boolean showClose = hasText || (mIconifiedByDefault && !mExpandedInActionView);
mCloseButton.setVisibility(showClose ? VISIBLE : GONE);
- mCloseButton.getDrawable().setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET);
+ final Drawable closeButtonImg = mCloseButton.getDrawable();
+ if (closeButtonImg != null){
+ closeButtonImg.setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET);
+ }
}
private void postUpdateFocusedState() {
@@ -854,9 +868,16 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
}
private void updateFocusedState() {
- boolean focused = mQueryTextView.hasFocus();
- mSearchPlate.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET);
- mSubmitArea.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET);
+ final boolean focused = mSearchSrcTextView.hasFocus();
+ final int[] stateSet = focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET;
+ final Drawable searchPlateBg = mSearchPlate.getBackground();
+ if (searchPlateBg != null) {
+ searchPlateBg.setState(stateSet);
+ }
+ final Drawable submitAreaBg = mSubmitArea.getBackground();
+ if (submitAreaBg != null) {
+ submitAreaBg.setState(stateSet);
+ }
invalidate();
}
@@ -896,11 +917,11 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
onSearchClicked();
} else if (v == mCloseButton) {
onCloseClicked();
- } else if (v == mSubmitButton) {
+ } else if (v == mGoButton) {
onSubmitQuery();
} else if (v == mVoiceButton) {
onVoiceClicked();
- } else if (v == mQueryTextView) {
+ } else if (v == mSearchSrcTextView) {
forceSuggestionQuery();
}
}
@@ -925,7 +946,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
// entered query with the action key
SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
- launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView.getText()
+ launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mSearchSrcTextView.getText()
.toString());
return true;
}
@@ -947,25 +968,25 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
if (DBG) {
Log.d(LOG_TAG, "mTextListener.onKey(" + keyCode + "," + event + "), selection: "
- + mQueryTextView.getListSelection());
+ + mSearchSrcTextView.getListSelection());
}
// If a suggestion is selected, handle enter, search key, and action keys
// as presses on the selected suggestion
- if (mQueryTextView.isPopupShowing()
- && mQueryTextView.getListSelection() != ListView.INVALID_POSITION) {
+ if (mSearchSrcTextView.isPopupShowing()
+ && mSearchSrcTextView.getListSelection() != ListView.INVALID_POSITION) {
return onSuggestionsKey(v, keyCode, event);
}
// If there is text in the query box, handle enter, and action keys
// The search key is handled by the dialog's onKeyDown().
- if (!mQueryTextView.isEmpty() && event.hasNoModifiers()) {
+ if (!mSearchSrcTextView.isEmpty() && event.hasNoModifiers()) {
if (event.getAction() == KeyEvent.ACTION_UP) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
v.cancelLongPress();
// Launch as a regular search.
- launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, mQueryTextView.getText()
+ launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, mSearchSrcTextView.getText()
.toString());
return true;
}
@@ -973,7 +994,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
- launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView
+ launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mSearchSrcTextView
.getText().toString());
return true;
}
@@ -1001,7 +1022,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
// "click")
if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH
|| keyCode == KeyEvent.KEYCODE_TAB) {
- int position = mQueryTextView.getListSelection();
+ int position = mSearchSrcTextView.getListSelection();
return onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null);
}
@@ -1012,18 +1033,18 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
// left key, at end if right key
// TODO: Reverse left/right for right-to-left languages, e.g.
// Arabic
- int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mQueryTextView
+ int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mSearchSrcTextView
.length();
- mQueryTextView.setSelection(selPoint);
- mQueryTextView.setListSelection(0);
- mQueryTextView.clearListSelection();
- mQueryTextView.ensureImeVisible(true);
+ mSearchSrcTextView.setSelection(selPoint);
+ mSearchSrcTextView.setListSelection(0);
+ mSearchSrcTextView.clearListSelection();
+ mSearchSrcTextView.ensureImeVisible(true);
return true;
}
// Next, check for an "up and out" move
- if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mQueryTextView.getListSelection()) {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mSearchSrcTextView.getListSelection()) {
// TODO: restoreUserQuery();
// let ACTV complete the move
return false;
@@ -1035,7 +1056,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
&& ((actionKey.getSuggestActionMsg() != null) || (actionKey
.getSuggestActionMsgColumn() != null))) {
// launch suggestion using action key column
- int position = mQueryTextView.getListSelection();
+ int position = mSearchSrcTextView.getListSelection();
if (position != ListView.INVALID_POSITION) {
Cursor c = mSuggestionsAdapter.getCursor();
if (c.moveToPosition(position)) {
@@ -1078,24 +1099,24 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
}
private CharSequence getDecoratedHint(CharSequence hintText) {
- // If the field is always expanded, then don't add the search icon to the hint
- if (!mIconifiedByDefault) {
+ // If the field is always expanded or we don't have a search hint icon,
+ // then don't add the search icon to the hint.
+ if (!mIconifiedByDefault || mSearchHintIcon == null) {
return hintText;
}
- final Drawable searchIcon = getContext().getDrawable(mSearchIconResId);
- final int textSize = (int) (mQueryTextView.getTextSize() * 1.25);
- searchIcon.setBounds(0, 0, textSize, textSize);
+ final int textSize = (int) (mSearchSrcTextView.getTextSize() * 1.25);
+ mSearchHintIcon.setBounds(0, 0, textSize, textSize);
- final SpannableStringBuilder ssb = new SpannableStringBuilder(" "); // for the icon
+ final SpannableStringBuilder ssb = new SpannableStringBuilder(" ");
+ ssb.setSpan(new ImageSpan(mSearchHintIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.append(hintText);
- ssb.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return ssb;
}
private void updateQueryHint() {
if (mQueryHint != null) {
- mQueryTextView.setHint(getDecoratedHint(mQueryHint));
+ mSearchSrcTextView.setHint(getDecoratedHint(mQueryHint));
} else if (mSearchable != null) {
CharSequence hint = null;
int hintId = mSearchable.getHintId();
@@ -1103,10 +1124,10 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
hint = getContext().getString(hintId);
}
if (hint != null) {
- mQueryTextView.setHint(getDecoratedHint(hint));
+ mSearchSrcTextView.setHint(getDecoratedHint(hint));
}
} else {
- mQueryTextView.setHint(getDecoratedHint(""));
+ mSearchSrcTextView.setHint(getDecoratedHint(""));
}
}
@@ -1114,9 +1135,9 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
* Updates the auto-complete text view.
*/
private void updateSearchAutoComplete() {
- mQueryTextView.setDropDownAnimationStyle(0); // no animation
- mQueryTextView.setThreshold(mSearchable.getSuggestThreshold());
- mQueryTextView.setImeOptions(mSearchable.getImeOptions());
+ mSearchSrcTextView.setDropDownAnimationStyle(0); // no animation
+ mSearchSrcTextView.setThreshold(mSearchable.getSuggestThreshold());
+ mSearchSrcTextView.setImeOptions(mSearchable.getImeOptions());
int inputType = mSearchable.getInputType();
// We only touch this if the input type is set up for text (which it almost certainly
// should be, in the case of search!)
@@ -1135,7 +1156,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
}
}
- mQueryTextView.setInputType(inputType);
+ mSearchSrcTextView.setInputType(inputType);
if (mSuggestionsAdapter != null) {
mSuggestionsAdapter.changeCursor(null);
}
@@ -1144,7 +1165,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
if (mSearchable.getSuggestAuthority() != null) {
mSuggestionsAdapter = new SuggestionsAdapter(getContext(),
this, mSearchable, mOutsideDrawablesCache);
- mQueryTextView.setAdapter(mSuggestionsAdapter);
+ mSearchSrcTextView.setAdapter(mSuggestionsAdapter);
((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
mQueryRefinement ? SuggestionsAdapter.REFINE_ALL
: SuggestionsAdapter.REFINE_BY_ENTRY);
@@ -1161,7 +1182,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
int visibility = GONE;
if (mVoiceButtonEnabled && !isIconified() && empty) {
visibility = VISIBLE;
- mSubmitButton.setVisibility(GONE);
+ mGoButton.setVisibility(GONE);
}
mVoiceButton.setVisibility(visibility);
}
@@ -1178,7 +1199,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
};
private void onTextChanged(CharSequence newText) {
- CharSequence text = mQueryTextView.getText();
+ CharSequence text = mSearchSrcTextView.getText();
mUserQuery = text;
boolean hasText = !TextUtils.isEmpty(text);
updateSubmitButton(hasText);
@@ -1192,7 +1213,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
}
private void onSubmitQuery() {
- CharSequence query = mQueryTextView.getText();
+ CharSequence query = mSearchSrcTextView.getText();
if (query != null && TextUtils.getTrimmedLength(query) > 0) {
if (mOnQueryChangeListener == null
|| !mOnQueryChangeListener.onQueryTextSubmit(query.toString())) {
@@ -1206,11 +1227,11 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
}
private void dismissSuggestions() {
- mQueryTextView.dismissDropDown();
+ mSearchSrcTextView.dismissDropDown();
}
private void onCloseClicked() {
- CharSequence text = mQueryTextView.getText();
+ CharSequence text = mSearchSrcTextView.getText();
if (TextUtils.isEmpty(text)) {
if (mIconifiedByDefault) {
// If the app doesn't override the close behavior
@@ -1222,8 +1243,8 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
}
}
} else {
- mQueryTextView.setText("");
- mQueryTextView.requestFocus();
+ mSearchSrcTextView.setText("");
+ mSearchSrcTextView.requestFocus();
setImeVisibility(true);
}
@@ -1231,7 +1252,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
private void onSearchClicked() {
updateViewsVisibility(false);
- mQueryTextView.requestFocus();
+ mSearchSrcTextView.requestFocus();
setImeVisibility(true);
if (mOnSearchClickListener != null) {
mOnSearchClickListener.onClick(this);
@@ -1266,7 +1287,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
// Delayed update to make sure that the focus has settled down and window focus changes
// don't affect it. A synchronous update was not working.
postUpdateFocusedState();
- if (mQueryTextView.hasFocus()) {
+ if (mSearchSrcTextView.hasFocus()) {
forceSuggestionQuery();
}
}
@@ -1286,7 +1307,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
setQuery("", false);
clearFocus();
updateViewsVisibility(true);
- mQueryTextView.setImeOptions(mCollapsedImeOptions);
+ mSearchSrcTextView.setImeOptions(mCollapsedImeOptions);
mExpandedInActionView = false;
}
@@ -1298,9 +1319,9 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
if (mExpandedInActionView) return;
mExpandedInActionView = true;
- mCollapsedImeOptions = mQueryTextView.getImeOptions();
- mQueryTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN);
- mQueryTextView.setText("");
+ mCollapsedImeOptions = mSearchSrcTextView.getImeOptions();
+ mSearchSrcTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN);
+ mSearchSrcTextView.setText("");
setIconified(false);
}
@@ -1326,17 +1347,17 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
? res.getDimensionPixelSize(R.dimen.dropdownitem_icon_width)
+ res.getDimensionPixelSize(R.dimen.dropdownitem_text_padding_left)
: 0;
- mQueryTextView.getDropDownBackground().getPadding(dropDownPadding);
+ mSearchSrcTextView.getDropDownBackground().getPadding(dropDownPadding);
int offset;
if (isLayoutRtl) {
offset = - dropDownPadding.left;
} else {
offset = anchorPadding - (dropDownPadding.left + iconOffset);
}
- mQueryTextView.setDropDownHorizontalOffset(offset);
+ mSearchSrcTextView.setDropDownHorizontalOffset(offset);
final int width = mDropDownAnchor.getWidth() + dropDownPadding.left
+ dropDownPadding.right + iconOffset - anchorPadding;
- mQueryTextView.setDropDownWidth(width);
+ mSearchSrcTextView.setDropDownWidth(width);
}
}
@@ -1394,7 +1415,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
* Query rewriting.
*/
private void rewriteQueryFromSuggestion(int position) {
- CharSequence oldQuery = mQueryTextView.getText();
+ CharSequence oldQuery = mSearchSrcTextView.getText();
Cursor c = mSuggestionsAdapter.getCursor();
if (c == null) {
return;
@@ -1460,9 +1481,9 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
* Sets the text in the query box, without updating the suggestions.
*/
private void setQuery(CharSequence query) {
- mQueryTextView.setText(query, true);
+ mSearchSrcTextView.setText(query, true);
// Move the cursor to the end
- mQueryTextView.setSelection(TextUtils.isEmpty(query) ? 0 : query.length());
+ mSearchSrcTextView.setSelection(TextUtils.isEmpty(query) ? 0 : query.length());
}
private void launchQuerySearch(int actionKey, String actionMsg, String query) {
@@ -1648,8 +1669,8 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
}
private void forceSuggestionQuery() {
- mQueryTextView.doBeforeTextChanged();
- mQueryTextView.doAfterTextChanged();
+ mSearchSrcTextView.doBeforeTextChanged();
+ mSearchSrcTextView.doAfterTextChanged();
}
static boolean isLandscapeMode(Context context) {
diff --git a/core/java/android/widget/SimpleMonthAdapter.java b/core/java/android/widget/SimpleMonthAdapter.java
index 3bad235..24ebb2c 100644
--- a/core/java/android/widget/SimpleMonthAdapter.java
+++ b/core/java/android/widget/SimpleMonthAdapter.java
@@ -16,31 +16,52 @@
package android.widget;
+import com.android.internal.R;
+
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.SimpleMonthView.OnDayClickListener;
import java.util.Calendar;
-import java.util.HashMap;
/**
* An adapter for a list of {@link android.widget.SimpleMonthView} items.
*/
-class SimpleMonthAdapter extends BaseAdapter implements SimpleMonthView.OnDayClickListener {
- private static final String TAG = "SimpleMonthAdapter";
+class SimpleMonthAdapter extends BaseAdapter {
+ private final Calendar mMinDate = Calendar.getInstance();
+ private final Calendar mMaxDate = Calendar.getInstance();
private final Context mContext;
- private final DatePickerController mController;
- private Calendar mSelectedDay;
- private ColorStateList mCalendarTextColors;
+ private Calendar mSelectedDay = Calendar.getInstance();
+ private ColorStateList mCalendarTextColors = ColorStateList.valueOf(Color.BLACK);
+ private OnDaySelectedListener mOnDaySelectedListener;
+
+ private int mFirstDayOfWeek;
- public SimpleMonthAdapter(Context context, DatePickerController controller) {
+ public SimpleMonthAdapter(Context context) {
mContext = context;
- mController = controller;
- init();
- setSelectedDay(mController.getSelectedDay());
+ }
+
+ public void setRange(Calendar min, Calendar max) {
+ mMinDate.setTimeInMillis(min.getTimeInMillis());
+ mMaxDate.setTimeInMillis(max.getTimeInMillis());
+
+ notifyDataSetInvalidated();
+ }
+
+ public void setFirstDayOfWeek(int firstDayOfWeek) {
+ mFirstDayOfWeek = firstDayOfWeek;
+
+ notifyDataSetInvalidated();
+ }
+
+ public int getFirstDayOfWeek() {
+ return mFirstDayOfWeek;
}
/**
@@ -49,10 +70,18 @@ class SimpleMonthAdapter extends BaseAdapter implements SimpleMonthView.OnDayCli
* @param day The day to highlight
*/
public void setSelectedDay(Calendar day) {
- if (mSelectedDay != day) {
- mSelectedDay = day;
- notifyDataSetChanged();
- }
+ mSelectedDay = day;
+
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Sets the listener to call when the user selects a day.
+ *
+ * @param listener The listener to call.
+ */
+ public void setOnDaySelectedListener(OnDaySelectedListener listener) {
+ mOnDaySelectedListener = listener;
}
void setCalendarTextColor(ColorStateList colors) {
@@ -60,18 +89,28 @@ class SimpleMonthAdapter extends BaseAdapter implements SimpleMonthView.OnDayCli
}
/**
- * Set up the gesture detector and selected time
+ * Sets the text color, size, style, hint color, and highlight color from
+ * the specified TextAppearance resource. This is mostly copied from
+ * {@link TextView#setTextAppearance(Context, int)}.
*/
- protected void init() {
- mSelectedDay = Calendar.getInstance();
+ void setCalendarTextAppearance(int resId) {
+ final TypedArray a = mContext.obtainStyledAttributes(resId, R.styleable.TextAppearance);
+
+ final ColorStateList textColor = a.getColorStateList(R.styleable.TextAppearance_textColor);
+ if (textColor != null) {
+ mCalendarTextColors = textColor;
+ }
+
+ // TODO: Support font size, etc.
+
+ a.recycle();
}
@Override
public int getCount() {
- final int diffYear = mController.getMaxYear() - mController.getMinYear();
- final int diffMonth = 1 + mController.getMaxMonth() - mController.getMinMonth()
- + 12 * diffYear;
- return diffMonth;
+ final int diffYear = mMaxDate.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR);
+ final int diffMonth = mMaxDate.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH);
+ return diffMonth + 12 * diffYear + 1;
}
@Override
@@ -92,36 +131,34 @@ class SimpleMonthAdapter extends BaseAdapter implements SimpleMonthView.OnDayCli
@SuppressWarnings("unchecked")
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- SimpleMonthView v;
- HashMap<String, Integer> drawingParams = null;
+ final SimpleMonthView v;
if (convertView != null) {
v = (SimpleMonthView) convertView;
- // We store the drawing parameters in the view so it can be recycled
- drawingParams = (HashMap<String, Integer>) v.getTag();
} else {
v = new SimpleMonthView(mContext);
+
// Set up the new view
- AbsListView.LayoutParams params = new AbsListView.LayoutParams(
+ final AbsListView.LayoutParams params = new AbsListView.LayoutParams(
AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.MATCH_PARENT);
v.setLayoutParams(params);
v.setClickable(true);
- v.setOnDayClickListener(this);
+ v.setOnDayClickListener(mOnDayClickListener);
+
if (mCalendarTextColors != null) {
v.setTextColor(mCalendarTextColors);
}
}
- if (drawingParams == null) {
- drawingParams = new HashMap<String, Integer>();
- } else {
- drawingParams.clear();
- }
- final int currentMonth = position + mController.getMinMonth();
- final int month = currentMonth % 12;
- final int year = currentMonth / 12 + mController.getMinYear();
- int selectedDay = -1;
+ final int minMonth = mMinDate.get(Calendar.MONTH);
+ final int minYear = mMinDate.get(Calendar.YEAR);
+ final int currentMonth = position + minMonth;
+ final int month = currentMonth % 12;
+ final int year = currentMonth / 12 + minYear;
+ final int selectedDay;
if (isSelectedDayInMonth(year, month)) {
selectedDay = mSelectedDay.get(Calendar.DAY_OF_MONTH);
+ } else {
+ selectedDay = -1;
}
// Invokes requestLayout() to ensure that the recycled view is set with the appropriate
@@ -129,20 +166,20 @@ class SimpleMonthAdapter extends BaseAdapter implements SimpleMonthView.OnDayCli
v.reuse();
final int enabledDayRangeStart;
- if (mController.getMinMonth() == month && mController.getMinYear() == year) {
- enabledDayRangeStart = mController.getMinDay();
+ if (minMonth == month && minYear == year) {
+ enabledDayRangeStart = mMinDate.get(Calendar.DAY_OF_MONTH);
} else {
enabledDayRangeStart = 1;
}
final int enabledDayRangeEnd;
- if (mController.getMaxMonth() == month && mController.getMaxYear() == year) {
- enabledDayRangeEnd = mController.getMaxDay();
+ if (mMaxDate.get(Calendar.MONTH) == month && mMaxDate.get(Calendar.YEAR) == year) {
+ enabledDayRangeEnd = mMaxDate.get(Calendar.DAY_OF_MONTH);
} else {
enabledDayRangeEnd = 31;
}
- v.setMonthParams(selectedDay, month, year, mController.getFirstDayOfWeek(),
+ v.setMonthParams(selectedDay, month, year, mFirstDayOfWeek,
enabledDayRangeStart, enabledDayRangeEnd);
v.invalidate();
@@ -153,22 +190,24 @@ class SimpleMonthAdapter extends BaseAdapter implements SimpleMonthView.OnDayCli
return mSelectedDay.get(Calendar.YEAR) == year && mSelectedDay.get(Calendar.MONTH) == month;
}
- @Override
- public void onDayClick(SimpleMonthView view, Calendar day) {
- if (day != null) {
- onDayTapped(day);
- }
+ private boolean isCalendarInRange(Calendar value) {
+ return value.compareTo(mMinDate) >= 0 && value.compareTo(mMaxDate) <= 0;
}
- /**
- * Maintains the same hour/min/sec but moves the day to the tapped day.
- *
- * @param day The day that was tapped
- */
- protected void onDayTapped(Calendar day) {
- mController.tryVibrate();
- mController.onDayOfMonthSelected(day.get(Calendar.YEAR), day.get(Calendar.MONTH),
- day.get(Calendar.DAY_OF_MONTH));
- setSelectedDay(day);
+ private final OnDayClickListener mOnDayClickListener = new OnDayClickListener() {
+ @Override
+ public void onDayClick(SimpleMonthView view, Calendar day) {
+ if (day != null && isCalendarInRange(day)) {
+ setSelectedDay(day);
+
+ if (mOnDaySelectedListener != null) {
+ mOnDaySelectedListener.onDaySelected(SimpleMonthAdapter.this, day);
+ }
+ }
+ }
+ };
+
+ public interface OnDaySelectedListener {
+ public void onDaySelected(SimpleMonthAdapter view, Calendar day);
}
}
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index a76241e..d2a37ac 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -31,6 +31,7 @@ import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.AttributeSet;
+import android.util.IntArray;
import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.View;
@@ -51,8 +52,6 @@ import java.util.Locale;
* within the specified month.
*/
class SimpleMonthView extends View {
- private static final String TAG = "SimpleMonthView";
-
private static final int DEFAULT_HEIGHT = 32;
private static final int MIN_HEIGHT = 10;
@@ -66,15 +65,15 @@ class SimpleMonthView extends View {
private static final int DAY_SEPARATOR_WIDTH = 1;
+ private final Formatter mFormatter;
+ private final StringBuilder mStringBuilder;
+
private final int mMiniDayNumberTextSize;
private final int mMonthLabelTextSize;
private final int mMonthDayLabelTextSize;
private final int mMonthHeaderSize;
private final int mDaySelectedCircleSize;
- // used for scaling to the device density
- private static float mScale = 0;
-
/** Single-letter (when available) formatter for the day of week label. */
private SimpleDateFormat mDayFormatter = new SimpleDateFormat("EEEEE", Locale.getDefault());
@@ -91,9 +90,6 @@ class SimpleMonthView extends View {
private Paint mMonthTitlePaint;
private Paint mMonthDayLabelPaint;
- private final Formatter mFormatter;
- private final StringBuilder mStringBuilder;
-
private int mMonth;
private int mYear;
@@ -154,11 +150,14 @@ class SimpleMonthView extends View {
this(context, attrs, R.attr.datePickerStyle);
}
- public SimpleMonthView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs);
+ public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
- final Resources res = context.getResources();
+ public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ final Resources res = context.getResources();
mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface);
mMonthTitleTypeface = res.getString(R.string.sans_serif);
@@ -610,7 +609,7 @@ class SimpleMonthView extends View {
}
@Override
- protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
+ protected void getVisibleVirtualViews(IntArray virtualViewIds) {
for (int day = 1; day <= mNumCells; day++) {
virtualViewIds.add(day);
}
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index a898084..7a22224 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -39,6 +39,7 @@ import android.util.FloatProperty;
import android.util.MathUtils;
import android.view.Gravity;
import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
@@ -688,6 +689,10 @@ public class Switch extends CompoundButton {
* @return true if (x, y) is within the target area of the switch thumb
*/
private boolean hitThumb(float x, float y) {
+ if (mThumbDrawable == null) {
+ return false;
+ }
+
// Relies on mTempRect, MUST be called first!
final int thumbOffset = getThumbOffset();
@@ -797,6 +802,7 @@ public class Switch extends CompoundButton {
// Commit the change if the event is up and not canceled and the switch
// has not been disabled during the drag.
final boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
+ final boolean oldState = isChecked();
final boolean newState;
if (commitChange) {
mVelocityTracker.computeCurrentVelocity(1000);
@@ -807,10 +813,14 @@ public class Switch extends CompoundButton {
newState = getTargetCheckedState();
}
} else {
- newState = isChecked();
+ newState = oldState;
+ }
+
+ if (newState != oldState) {
+ playSoundEffect(SoundEffectConstants.CLICK);
+ setChecked(newState);
}
- setChecked(newState);
cancelSuperTouch(ev);
}
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 1a86809..e2acaac 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,6 +27,7 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.AttributeSet;
@@ -127,6 +129,8 @@ public class TextClock extends TextView {
private Calendar mTime;
private String mTimeZone;
+ private boolean mShowCurrentUserTime;
+
private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
@@ -342,6 +346,22 @@ public class TextClock extends TextView {
}
/**
+ * Sets whether this clock should always track the current user and not the user of the
+ * current process. This is used for single instance processes like the systemUI who need
+ * to display time for different users.
+ *
+ * @hide
+ */
+ public void setShowCurrentUserTime(boolean showCurrentUserTime) {
+ mShowCurrentUserTime = showCurrentUserTime;
+
+ chooseFormat();
+ onTimeChanged();
+ unregisterObserver();
+ registerObserver();
+ }
+
+ /**
* Indicates whether the system is currently using the 24-hour mode.
*
* When the system is in 24-hour mode, this view will use the pattern
@@ -360,7 +380,11 @@ public class TextClock extends TextView {
* @see #getFormat24Hour()
*/
public boolean is24HourModeEnabled() {
- return DateFormat.is24HourFormat(getContext());
+ if (mShowCurrentUserTime) {
+ return DateFormat.is24HourFormat(getContext(), ActivityManager.getCurrentUser());
+ } else {
+ return DateFormat.is24HourFormat(getContext());
+ }
}
/**
@@ -500,7 +524,13 @@ public class TextClock extends TextView {
private void registerObserver() {
final ContentResolver resolver = getContext().getContentResolver();
- resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver);
+ if (mShowCurrentUserTime) {
+ resolver.registerContentObserver(Settings.System.CONTENT_URI, true,
+ mFormatChangeObserver, UserHandle.USER_ALL);
+ } else {
+ resolver.registerContentObserver(Settings.System.CONTENT_URI, true,
+ mFormatChangeObserver);
+ }
}
private void unregisterReceiver() {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index dc4d214..dd8280b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -42,6 +42,7 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.provider.Settings;
import android.text.BoringLayout;
import android.text.DynamicLayout;
@@ -286,9 +287,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mCurTextColor;
private int mCurHintTextColor;
private boolean mFreezesText;
- private boolean mTemporaryDetach;
private boolean mDispatchTemporaryDetach;
+ /** Whether this view is temporarily detached from the parent view. */
+ boolean mTemporaryDetach;
+
private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
@@ -654,6 +657,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
ColorStateList textColorLink = null;
int textSize = 15;
String fontFamily = null;
+ boolean fontFamilyExplicit = false;
int typefaceIndex = -1;
int styleIndex = -1;
boolean allCaps = false;
@@ -1008,6 +1012,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case com.android.internal.R.styleable.TextView_fontFamily:
fontFamily = a.getString(attr);
+ fontFamilyExplicit = true;
break;
case com.android.internal.R.styleable.TextView_password:
@@ -1296,6 +1301,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
typefaceIndex = MONOSPACE;
}
+ if (typefaceIndex != -1 && !fontFamilyExplicit) {
+ fontFamily = null;
+ }
setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
if (shadowcolor != 0) {
@@ -3631,9 +3639,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (mHintTextColor != null) {
color = mHintTextColor.getColorForState(getDrawableState(), 0);
- if (color != mCurHintTextColor && mText.length() == 0) {
+ if (color != mCurHintTextColor) {
mCurHintTextColor = color;
- inval = true;
+ if (mText.length() == 0) {
+ inval = true;
+ }
}
}
if (inval) {
@@ -5013,9 +5023,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return (int) Math.max(0, mShadowDy + mShadowRadius);
}
+ private int getFudgedPaddingRight() {
+ // Add sufficient space for cursor and tone marks
+ int cursorWidth = 2 + (int)mTextPaint.density; // adequate for Material cursors
+ return Math.max(0, getCompoundPaddingRight() - (cursorWidth - 1));
+ }
+
@Override
protected int getRightPaddingOffset() {
- return -(getCompoundPaddingRight() - mPaddingRight) +
+ return -(getFudgedPaddingRight() - mPaddingRight) +
(int) Math.max(0, mShadowDx + mShadowRadius);
}
@@ -5371,7 +5387,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
float clipLeft = compoundPaddingLeft + scrollX;
float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
- float clipRight = right - left - compoundPaddingRight + scrollX;
+ float clipRight = right - left - getFudgedPaddingRight() + scrollX;
float clipBottom = bottom - top + scrollY -
((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
@@ -8383,8 +8399,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* to speak passwords.
*/
private boolean shouldSpeakPasswordsForAccessibility() {
- return (Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) == 1);
+ return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
+ UserHandle.USER_CURRENT_OR_SELF) == 1);
}
@Override
@@ -8466,8 +8483,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ /**
+ * Performs an accessibility action after it has been offered to the
+ * delegate.
+ *
+ * @hide
+ */
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfo.ACTION_CLICK: {
boolean handled = false;
@@ -8519,6 +8542,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} return false;
case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
if (isFocused() && canSelectText()) {
+ ensureIterableTextForAccessibilitySelectable();
CharSequence text = getIterableTextForAccessibility();
if (text == null) {
return false;
@@ -8544,8 +8568,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
} return false;
+ case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
+ case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
+ ensureIterableTextForAccessibilitySelectable();
+ return super.performAccessibilityActionInternal(action, arguments);
+ }
default: {
- return super.performAccessibilityAction(action, arguments);
+ return super.performAccessibilityActionInternal(action, arguments);
}
}
}
@@ -9033,10 +9062,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
@Override
public CharSequence getIterableTextForAccessibility() {
+ return mText;
+ }
+
+ private void ensureIterableTextForAccessibilitySelectable() {
if (!(mText instanceof Spannable)) {
setText(mText, BufferType.SPANNABLE);
}
- return mText;
}
/**
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 85cf67b..26e02f8 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -86,12 +86,12 @@ public class TimePicker extends FrameLayout {
switch (mode) {
case MODE_CLOCK:
- mDelegate = new TimePickerSpinnerDelegate(
+ mDelegate = new TimePickerClockDelegate(
this, context, attrs, defStyleAttr, defStyleRes);
break;
case MODE_SPINNER:
default:
- mDelegate = new TimePickerClockDelegate(
+ mDelegate = new TimePickerSpinnerDelegate(
this, context, attrs, defStyleAttr, defStyleRes);
break;
}
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 8917f39..1534429 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -17,365 +17,390 @@
package android.widget;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.HapticFeedbackConstants;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.AccessibilityDelegate;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
import com.android.internal.R;
-import java.text.DateFormatSymbols;
+import java.util.ArrayList;
import java.util.Calendar;
import java.util.Locale;
-import libcore.icu.LocaleData;
-
-import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
-import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
-
/**
- * A delegate implementing the basic TimePicker
+ * A delegate implementing the radial clock-based TimePicker.
*/
-class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
+class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate implements
+ RadialTimePickerView.OnValueSelectedListener {
+
+ private static final String TAG = "TimePickerClockDelegate";
+
+ // Index used by RadialPickerLayout
+ private static final int HOUR_INDEX = 0;
+ private static final int MINUTE_INDEX = 1;
+
+ // NOT a real index for the purpose of what's showing.
+ private static final int AMPM_INDEX = 2;
+
+ // Also NOT a real index, just used for keyboard mode.
+ private static final int ENABLE_PICKER_INDEX = 3;
+
+ // LayoutLib relies on these constants. Change TimePickerClockDelegate_Delegate if
+ // modifying these.
+ static final int AM = 0;
+ static final int PM = 1;
+
private static final boolean DEFAULT_ENABLED_STATE = true;
+ private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
+
private static final int HOURS_IN_HALF_DAY = 12;
- // state
+ private final View mHeaderView;
+ private final TextView mHourView;
+ private final TextView mMinuteView;
+ private final View mAmPmLayout;
+ private final CheckedTextView mAmLabel;
+ private final CheckedTextView mPmLabel;
+ private final RadialTimePickerView mRadialTimePickerView;
+ private final TextView mSeparatorView;
+
+ private final String mAmText;
+ private final String mPmText;
+
+ private final float mDisabledAlpha;
+
+ private boolean mAllowAutoAdvance;
+ private int mInitialHourOfDay;
+ private int mInitialMinute;
private boolean mIs24HourView;
- private boolean mIsAm;
- // ui components
- private final NumberPicker mHourSpinner;
- private final NumberPicker mMinuteSpinner;
- private final NumberPicker mAmPmSpinner;
- private final EditText mHourSpinnerInput;
- private final EditText mMinuteSpinnerInput;
- private final EditText mAmPmSpinnerInput;
- private final TextView mDivider;
+ // For hardware IME input.
+ private char mPlaceholderText;
+ private String mDoublePlaceholderText;
+ private String mDeletedKeyFormat;
+ private boolean mInKbMode;
+ private ArrayList<Integer> mTypedTimes = new ArrayList<Integer>();
+ private Node mLegalTimesTree;
+ private int mAmKeyCode;
+ private int mPmKeyCode;
- // Note that the legacy implementation of the TimePicker is
- // using a button for toggling between AM/PM while the new
- // version uses a NumberPicker spinner. Therefore the code
- // accommodates these two cases to be backwards compatible.
- private final Button mAmPmButton;
+ // Accessibility strings.
+ private String mSelectHours;
+ private String mSelectMinutes;
- private final String[] mAmPmStrings;
+ // Most recent time announcement values for accessibility.
+ private CharSequence mLastAnnouncedText;
+ private boolean mLastAnnouncedIsHour;
- private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
private Calendar mTempCalendar;
- private boolean mHourWithTwoDigit;
- private char mHourFormat;
public TimePickerClockDelegate(TimePicker delegator, Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(delegator, context);
// process style attributes
- final TypedArray a = mContext.obtainStyledAttributes(
- attrs, R.styleable.TimePicker, defStyleAttr, defStyleRes);
- final int layoutResourceId = a.getResourceId(
- R.styleable.TimePicker_legacyLayout, R.layout.time_picker_legacy);
- a.recycle();
+ final TypedArray a = mContext.obtainStyledAttributes(attrs,
+ R.styleable.TimePicker, defStyleAttr, defStyleRes);
+ final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ final Resources res = mContext.getResources();
+
+ mSelectHours = res.getString(R.string.select_hours);
+ mSelectMinutes = res.getString(R.string.select_minutes);
+
+ String[] amPmStrings = TimePickerSpinnerDelegate.getAmPmStrings(context);
+ mAmText = amPmStrings[0];
+ mPmText = amPmStrings[1];
+
+ final int layoutResourceId = a.getResourceId(R.styleable.TimePicker_internalLayout,
+ R.layout.time_picker_holo);
+ final View mainView = inflater.inflate(layoutResourceId, delegator);
+
+ mHeaderView = mainView.findViewById(R.id.time_header);
+ mHeaderView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground));
+
+ // Set up hour/minute labels.
+ mHourView = (TextView) mHeaderView.findViewById(R.id.hours);
+ mHourView.setOnClickListener(mClickListener);
+ mHourView.setAccessibilityDelegate(
+ new ClickActionDelegate(context, R.string.select_hours));
+ mSeparatorView = (TextView) mHeaderView.findViewById(R.id.separator);
+ mMinuteView = (TextView) mHeaderView.findViewById(R.id.minutes);
+ mMinuteView.setOnClickListener(mClickListener);
+ mMinuteView.setAccessibilityDelegate(
+ new ClickActionDelegate(context, R.string.select_minutes));
+
+ final int headerTimeTextAppearance = a.getResourceId(
+ R.styleable.TimePicker_headerTimeTextAppearance, 0);
+ if (headerTimeTextAppearance != 0) {
+ mHourView.setTextAppearance(context, headerTimeTextAppearance);
+ mSeparatorView.setTextAppearance(context, headerTimeTextAppearance);
+ mMinuteView.setTextAppearance(context, headerTimeTextAppearance);
+ }
- final LayoutInflater inflater = LayoutInflater.from(mContext);
- inflater.inflate(layoutResourceId, mDelegator, true);
-
- // hour
- mHourSpinner = (NumberPicker) delegator.findViewById(R.id.hour);
- mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
- public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
- updateInputState();
- if (!is24HourView()) {
- if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) ||
- (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) {
- mIsAm = !mIsAm;
- updateAmPmControl();
- }
- }
- onTimeChanged();
- }
- });
- mHourSpinnerInput = (EditText) mHourSpinner.findViewById(R.id.numberpicker_input);
- mHourSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
-
- // divider (only for the new widget style)
- mDivider = (TextView) mDelegator.findViewById(R.id.divider);
- if (mDivider != null) {
- setDividerText();
- }
-
- // minute
- mMinuteSpinner = (NumberPicker) mDelegator.findViewById(R.id.minute);
- mMinuteSpinner.setMinValue(0);
- mMinuteSpinner.setMaxValue(59);
- mMinuteSpinner.setOnLongPressUpdateInterval(100);
- mMinuteSpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
- mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
- public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
- updateInputState();
- int minValue = mMinuteSpinner.getMinValue();
- int maxValue = mMinuteSpinner.getMaxValue();
- if (oldVal == maxValue && newVal == minValue) {
- int newHour = mHourSpinner.getValue() + 1;
- if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) {
- mIsAm = !mIsAm;
- updateAmPmControl();
- }
- mHourSpinner.setValue(newHour);
- } else if (oldVal == minValue && newVal == maxValue) {
- int newHour = mHourSpinner.getValue() - 1;
- if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) {
- mIsAm = !mIsAm;
- updateAmPmControl();
- }
- mHourSpinner.setValue(newHour);
- }
- onTimeChanged();
- }
- });
- mMinuteSpinnerInput = (EditText) mMinuteSpinner.findViewById(R.id.numberpicker_input);
- mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
-
- // Get the localized am/pm strings and use them in the spinner.
- mAmPmStrings = getAmPmStrings(context);
-
- // am/pm
- final View amPmView = mDelegator.findViewById(R.id.amPm);
- if (amPmView instanceof Button) {
- mAmPmSpinner = null;
- mAmPmSpinnerInput = null;
- mAmPmButton = (Button) amPmView;
- mAmPmButton.setOnClickListener(new View.OnClickListener() {
- public void onClick(View button) {
- button.requestFocus();
- mIsAm = !mIsAm;
- updateAmPmControl();
- onTimeChanged();
- }
- });
- } else {
- mAmPmButton = null;
- mAmPmSpinner = (NumberPicker) amPmView;
- mAmPmSpinner.setMinValue(0);
- mAmPmSpinner.setMaxValue(1);
- mAmPmSpinner.setDisplayedValues(mAmPmStrings);
- mAmPmSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
- public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
- updateInputState();
- picker.requestFocus();
- mIsAm = !mIsAm;
- updateAmPmControl();
- onTimeChanged();
- }
- });
- mAmPmSpinnerInput = (EditText) mAmPmSpinner.findViewById(R.id.numberpicker_input);
- mAmPmSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
- }
-
- if (isAmPmAtStart()) {
- // Move the am/pm view to the beginning
- ViewGroup amPmParent = (ViewGroup) delegator.findViewById(R.id.timePickerLayout);
- amPmParent.removeView(amPmView);
- amPmParent.addView(amPmView, 0);
- // Swap layout margins if needed. They may be not symmetrical (Old Standard Theme
- // for example and not for Holo Theme)
- ViewGroup.MarginLayoutParams lp =
- (ViewGroup.MarginLayoutParams) amPmView.getLayoutParams();
- final int startMargin = lp.getMarginStart();
- final int endMargin = lp.getMarginEnd();
- if (startMargin != endMargin) {
- lp.setMarginStart(endMargin);
- lp.setMarginEnd(startMargin);
- }
+ // Now that we have text appearances out of the way, make sure the hour
+ // and minute views are correctly sized.
+ mHourView.setMinWidth(computeStableWidth(mHourView, 24));
+ mMinuteView.setMinWidth(computeStableWidth(mMinuteView, 60));
+
+ // TODO: This can be removed once we support themed color state lists.
+ final int headerSelectedTextColor = a.getColor(
+ R.styleable.TimePicker_headerSelectedTextColor,
+ res.getColor(R.color.timepicker_default_selector_color_material));
+ mHourView.setTextColor(ColorStateList.addFirstIfMissing(mHourView.getTextColors(),
+ R.attr.state_selected, headerSelectedTextColor));
+ mMinuteView.setTextColor(ColorStateList.addFirstIfMissing(mMinuteView.getTextColors(),
+ R.attr.state_selected, headerSelectedTextColor));
+
+ // Set up AM/PM labels.
+ mAmPmLayout = mHeaderView.findViewById(R.id.ampm_layout);
+ mAmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.am_label);
+ mAmLabel.setText(amPmStrings[0]);
+ mAmLabel.setOnClickListener(mClickListener);
+ mPmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.pm_label);
+ mPmLabel.setText(amPmStrings[1]);
+ mPmLabel.setOnClickListener(mClickListener);
+
+ final int headerAmPmTextAppearance = a.getResourceId(
+ R.styleable.TimePicker_headerAmPmTextAppearance, 0);
+ if (headerAmPmTextAppearance != 0) {
+ mAmLabel.setTextAppearance(context, headerAmPmTextAppearance);
+ mPmLabel.setTextAppearance(context, headerAmPmTextAppearance);
}
- getHourFormatData();
+ a.recycle();
+
+ // Pull disabled alpha from theme.
+ final TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
+ mDisabledAlpha = outValue.getFloat();
- // update controls to initial state
- updateHourControl();
- updateMinuteControl();
- updateAmPmControl();
+ mRadialTimePickerView = (RadialTimePickerView) mainView.findViewById(
+ R.id.radial_picker);
- // set to current time
- setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
- setCurrentMinute(mTempCalendar.get(Calendar.MINUTE));
+ setupListeners();
- if (!isEnabled()) {
- setEnabled(false);
+ mAllowAutoAdvance = true;
+
+ // Set up for keyboard mode.
+ mDoublePlaceholderText = res.getString(R.string.time_placeholder);
+ mDeletedKeyFormat = res.getString(R.string.deleted_key);
+ mPlaceholderText = mDoublePlaceholderText.charAt(0);
+ mAmKeyCode = mPmKeyCode = -1;
+ generateLegalTimesTree();
+
+ // Initialize with current time
+ final Calendar calendar = Calendar.getInstance(mCurrentLocale);
+ final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
+ final int currentMinute = calendar.get(Calendar.MINUTE);
+ initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX);
+ }
+
+ private static class ClickActionDelegate extends AccessibilityDelegate {
+ private final AccessibilityAction mClickAction;
+
+ public ClickActionDelegate(Context context, int resId) {
+ mClickAction = new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLICK, context.getString(resId));
}
- // set the content descriptions
- setContentDescriptions();
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
- // If not explicitly specified this view is important for accessibility.
- if (mDelegator.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
- mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ info.addAction(mClickAction);
}
}
- private void getHourFormatData() {
- final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
- (mIs24HourView) ? "Hm" : "hm");
- final int lengthPattern = bestDateTimePattern.length();
- mHourWithTwoDigit = false;
- char hourFormat = '\0';
- // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save
- // the hour format that we found.
- for (int i = 0; i < lengthPattern; i++) {
- final char c = bestDateTimePattern.charAt(i);
- if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
- mHourFormat = c;
- if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
- mHourWithTwoDigit = true;
- }
- break;
+ private int computeStableWidth(TextView v, int maxNumber) {
+ int maxWidth = 0;
+
+ for (int i = 0; i < maxNumber; i++) {
+ final String text = String.format("%02d", i);
+ v.setText(text);
+ v.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+
+ final int width = v.getMeasuredWidth();
+ if (width > maxWidth) {
+ maxWidth = width;
}
}
+
+ return maxWidth;
}
- private boolean isAmPmAtStart() {
- final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
- "hm" /* skeleton */);
+ private void initialize(int hourOfDay, int minute, boolean is24HourView, int index) {
+ mInitialHourOfDay = hourOfDay;
+ mInitialMinute = minute;
+ mIs24HourView = is24HourView;
+ mInKbMode = false;
+ updateUI(index);
+ }
+
+ private void setupListeners() {
+ mHeaderView.setOnKeyListener(mKeyListener);
+ mHeaderView.setOnFocusChangeListener(mFocusListener);
+ mHeaderView.setFocusable(true);
- return bestDateTimePattern.startsWith("a");
+ mRadialTimePickerView.setOnValueSelectedListener(this);
}
- /**
- * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":".
- *
- * See http://unicode.org/cldr/trac/browser/trunk/common/main
- *
- * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the
- * separator as the character which is just after the hour marker in the returned pattern.
- */
- private void setDividerText() {
- final String skeleton = (mIs24HourView) ? "Hm" : "hm";
- final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
- skeleton);
- final String separatorText;
- int hourIndex = bestDateTimePattern.lastIndexOf('H');
- if (hourIndex == -1) {
- hourIndex = bestDateTimePattern.lastIndexOf('h');
- }
- if (hourIndex == -1) {
- // Default case
- separatorText = ":";
+ private void updateUI(int index) {
+ // Update RadialPicker values
+ updateRadialPicker(index);
+ // Enable or disable the AM/PM view.
+ updateHeaderAmPm();
+ // Update Hour and Minutes
+ updateHeaderHour(mInitialHourOfDay, false);
+ // Update time separator
+ updateHeaderSeparator();
+ // Update Minutes
+ updateHeaderMinute(mInitialMinute, false);
+ // Invalidate everything
+ mDelegator.invalidate();
+ }
+
+ private void updateRadialPicker(int index) {
+ mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24HourView);
+ setCurrentItemShowing(index, false, true);
+ }
+
+ private void updateHeaderAmPm() {
+ if (mIs24HourView) {
+ mAmPmLayout.setVisibility(View.GONE);
} else {
- int minuteIndex = bestDateTimePattern.indexOf('m', hourIndex + 1);
- if (minuteIndex == -1) {
- separatorText = Character.toString(bestDateTimePattern.charAt(hourIndex + 1));
- } else {
- separatorText = bestDateTimePattern.substring(hourIndex + 1, minuteIndex);
+ // Ensure that AM/PM layout is in the correct position.
+ final String dateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, "hm");
+ final boolean amPmAtStart = dateTimePattern.startsWith("a");
+ final ViewGroup parent = (ViewGroup) mAmPmLayout.getParent();
+ final int targetIndex = amPmAtStart ? 0 : parent.getChildCount() - 1;
+ final int currentIndex = parent.indexOfChild(mAmPmLayout);
+ if (targetIndex != currentIndex) {
+ parent.removeView(mAmPmLayout);
+ parent.addView(mAmPmLayout, targetIndex);
}
+
+ updateAmPmLabelStates(mInitialHourOfDay < 12 ? AM : PM);
}
- mDivider.setText(separatorText);
}
+ /**
+ * Set the current hour.
+ */
@Override
public void setCurrentHour(Integer currentHour) {
- setCurrentHour(currentHour, true);
- }
-
- private void setCurrentHour(Integer currentHour, boolean notifyTimeChanged) {
- // why was Integer used in the first place?
- if (currentHour == null || currentHour == getCurrentHour()) {
+ if (mInitialHourOfDay == currentHour) {
return;
}
- if (!is24HourView()) {
- // convert [0,23] ordinal to wall clock display
- if (currentHour >= HOURS_IN_HALF_DAY) {
- mIsAm = false;
- if (currentHour > HOURS_IN_HALF_DAY) {
- currentHour = currentHour - HOURS_IN_HALF_DAY;
- }
- } else {
- mIsAm = true;
- if (currentHour == 0) {
- currentHour = HOURS_IN_HALF_DAY;
- }
- }
- updateAmPmControl();
- }
- mHourSpinner.setValue(currentHour);
- if (notifyTimeChanged) {
- onTimeChanged();
- }
+ mInitialHourOfDay = currentHour;
+ updateHeaderHour(currentHour, true);
+ updateHeaderAmPm();
+ mRadialTimePickerView.setCurrentHour(currentHour);
+ mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM);
+ mDelegator.invalidate();
+ onTimeChanged();
}
+ /**
+ * @return The current hour in the range (0-23).
+ */
@Override
public Integer getCurrentHour() {
- int currentHour = mHourSpinner.getValue();
- if (is24HourView()) {
+ int currentHour = mRadialTimePickerView.getCurrentHour();
+ if (mIs24HourView) {
return currentHour;
- } else if (mIsAm) {
- return currentHour % HOURS_IN_HALF_DAY;
} else {
- return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
+ switch(mRadialTimePickerView.getAmOrPm()) {
+ case PM:
+ return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
+ case AM:
+ default:
+ return currentHour % HOURS_IN_HALF_DAY;
+ }
}
}
+ /**
+ * Set the current minute (0-59).
+ */
@Override
public void setCurrentMinute(Integer currentMinute) {
- if (currentMinute == getCurrentMinute()) {
+ if (mInitialMinute == currentMinute) {
return;
}
- mMinuteSpinner.setValue(currentMinute);
+ mInitialMinute = currentMinute;
+ updateHeaderMinute(currentMinute, true);
+ mRadialTimePickerView.setCurrentMinute(currentMinute);
+ mDelegator.invalidate();
onTimeChanged();
}
+ /**
+ * @return The current minute.
+ */
@Override
public Integer getCurrentMinute() {
- return mMinuteSpinner.getValue();
+ return mRadialTimePickerView.getCurrentMinute();
}
+ /**
+ * Set whether in 24 hour or AM/PM mode.
+ *
+ * @param is24HourView True = 24 hour mode. False = AM/PM.
+ */
@Override
public void setIs24HourView(Boolean is24HourView) {
- if (mIs24HourView == is24HourView) {
+ if (is24HourView == mIs24HourView) {
return;
}
- // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!!
- int currentHour = getCurrentHour();
- // Order is important here.
mIs24HourView = is24HourView;
- getHourFormatData();
- updateHourControl();
- // set value after spinner range is updated
- setCurrentHour(currentHour, false);
- updateMinuteControl();
- updateAmPmControl();
+ generateLegalTimesTree();
+ int hour = mRadialTimePickerView.getCurrentHour();
+ mInitialHourOfDay = hour;
+ updateHeaderHour(hour, false);
+ updateHeaderAmPm();
+ updateRadialPicker(mRadialTimePickerView.getCurrentItemShowing());
+ mDelegator.invalidate();
}
+ /**
+ * @return true if this is in 24 hour view else false.
+ */
@Override
public boolean is24HourView() {
return mIs24HourView;
}
@Override
- public void setOnTimeChangedListener(TimePicker.OnTimeChangedListener onTimeChangedListener) {
- mOnTimeChangedListener = onTimeChangedListener;
+ public void setOnTimeChangedListener(TimePicker.OnTimeChangedListener callback) {
+ mOnTimeChangedListener = callback;
}
@Override
public void setEnabled(boolean enabled) {
- mMinuteSpinner.setEnabled(enabled);
- if (mDivider != null) {
- mDivider.setEnabled(enabled);
- }
- mHourSpinner.setEnabled(enabled);
- if (mAmPmSpinner != null) {
- mAmPmSpinner.setEnabled(enabled);
- } else {
- mAmPmButton.setEnabled(enabled);
- }
+ mHourView.setEnabled(enabled);
+ mMinuteView.setEnabled(enabled);
+ mAmLabel.setEnabled(enabled);
+ mPmLabel.setEnabled(enabled);
+ mRadialTimePickerView.setEnabled(enabled);
mIsEnabled = enabled;
}
@@ -386,24 +411,38 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
@Override
public int getBaseline() {
- return mHourSpinner.getBaseline();
+ // does not support baseline alignment
+ return -1;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
- setCurrentLocale(newConfig.locale);
+ updateUI(mRadialTimePickerView.getCurrentItemShowing());
}
@Override
public Parcelable onSaveInstanceState(Parcelable superState) {
- return new SavedState(superState, getCurrentHour(), getCurrentMinute());
+ return new SavedState(superState, getCurrentHour(), getCurrentMinute(),
+ is24HourView(), inKbMode(), getTypedTimes(), getCurrentItemShowing());
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
- setCurrentHour(ss.getHour());
- setCurrentMinute(ss.getMinute());
+ setInKbMode(ss.inKbMode());
+ setTypedTimes(ss.getTypesTimes());
+ initialize(ss.getHour(), ss.getMinute(), ss.is24HourMode(), ss.getCurrentItemShowing());
+ mRadialTimePickerView.invalidate();
+ if (mInKbMode) {
+ tryStartingKbMode(-1);
+ mHourView.invalidate();
+ }
+ }
+
+ @Override
+ public void setCurrentLocale(Locale locale) {
+ super.setCurrentLocale(locale);
+ mTempCalendar = Calendar.getInstance(locale);
}
@Override
@@ -422,9 +461,9 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
}
mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
- String selectedDateUtterance = DateUtils.formatDateTime(mContext,
+ String selectedDate = DateUtils.formatDateTime(mContext,
mTempCalendar.getTimeInMillis(), flags);
- event.getText().add(selectedDateUtterance);
+ event.getText().add(selectedDate);
}
@Override
@@ -437,121 +476,48 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
info.setClassName(TimePicker.class.getName());
}
- private void updateInputState() {
- // Make sure that if the user changes the value and the IME is active
- // for one of the inputs if this widget, the IME is closed. If the user
- // changed the value via the IME and there is a next input the IME will
- // be shown, otherwise the user chose another means of changing the
- // value and having the IME up makes no sense.
- InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
- if (inputMethodManager != null) {
- if (inputMethodManager.isActive(mHourSpinnerInput)) {
- mHourSpinnerInput.clearFocus();
- inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
- } else if (inputMethodManager.isActive(mMinuteSpinnerInput)) {
- mMinuteSpinnerInput.clearFocus();
- inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
- } else if (inputMethodManager.isActive(mAmPmSpinnerInput)) {
- mAmPmSpinnerInput.clearFocus();
- inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
- }
- }
- }
-
- private void updateAmPmControl() {
- if (is24HourView()) {
- if (mAmPmSpinner != null) {
- mAmPmSpinner.setVisibility(View.GONE);
- } else {
- mAmPmButton.setVisibility(View.GONE);
- }
- } else {
- int index = mIsAm ? Calendar.AM : Calendar.PM;
- if (mAmPmSpinner != null) {
- mAmPmSpinner.setValue(index);
- mAmPmSpinner.setVisibility(View.VISIBLE);
- } else {
- mAmPmButton.setText(mAmPmStrings[index]);
- mAmPmButton.setVisibility(View.VISIBLE);
- }
- }
- mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
- }
-
/**
- * Sets the current locale.
+ * Set whether in keyboard mode or not.
*
- * @param locale The current locale.
+ * @param inKbMode True means in keyboard mode.
*/
- @Override
- public void setCurrentLocale(Locale locale) {
- super.setCurrentLocale(locale);
- mTempCalendar = Calendar.getInstance(locale);
+ private void setInKbMode(boolean inKbMode) {
+ mInKbMode = inKbMode;
}
- private void onTimeChanged() {
- mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
- if (mOnTimeChangedListener != null) {
- mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(),
- getCurrentMinute());
- }
+ /**
+ * @return true if in keyboard mode
+ */
+ private boolean inKbMode() {
+ return mInKbMode;
}
- private void updateHourControl() {
- if (is24HourView()) {
- // 'k' means 1-24 hour
- if (mHourFormat == 'k') {
- mHourSpinner.setMinValue(1);
- mHourSpinner.setMaxValue(24);
- } else {
- mHourSpinner.setMinValue(0);
- mHourSpinner.setMaxValue(23);
- }
- } else {
- // 'K' means 0-11 hour
- if (mHourFormat == 'K') {
- mHourSpinner.setMinValue(0);
- mHourSpinner.setMaxValue(11);
- } else {
- mHourSpinner.setMinValue(1);
- mHourSpinner.setMaxValue(12);
- }
- }
- mHourSpinner.setFormatter(mHourWithTwoDigit ? NumberPicker.getTwoDigitFormatter() : null);
+ private void setTypedTimes(ArrayList<Integer> typeTimes) {
+ mTypedTimes = typeTimes;
}
- private void updateMinuteControl() {
- if (is24HourView()) {
- mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
- } else {
- mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
- }
+ /**
+ * @return an array of typed times
+ */
+ private ArrayList<Integer> getTypedTimes() {
+ return mTypedTimes;
}
- private void setContentDescriptions() {
- // Minute
- trySetContentDescription(mMinuteSpinner, R.id.increment,
- R.string.time_picker_increment_minute_button);
- trySetContentDescription(mMinuteSpinner, R.id.decrement,
- R.string.time_picker_decrement_minute_button);
- // Hour
- trySetContentDescription(mHourSpinner, R.id.increment,
- R.string.time_picker_increment_hour_button);
- trySetContentDescription(mHourSpinner, R.id.decrement,
- R.string.time_picker_decrement_hour_button);
- // AM/PM
- if (mAmPmSpinner != null) {
- trySetContentDescription(mAmPmSpinner, R.id.increment,
- R.string.time_picker_increment_set_pm_button);
- trySetContentDescription(mAmPmSpinner, R.id.decrement,
- R.string.time_picker_decrement_set_am_button);
- }
+ /**
+ * @return the index of the current item showing
+ */
+ private int getCurrentItemShowing() {
+ return mRadialTimePickerView.getCurrentItemShowing();
}
- private void trySetContentDescription(View root, int viewId, int contDescResId) {
- View target = root.findViewById(viewId);
- if (target != null) {
- target.setContentDescription(mContext.getString(contDescResId));
+ /**
+ * Propagate the time change
+ */
+ private void onTimeChanged() {
+ mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ if (mOnTimeChangedListener != null) {
+ mOnTimeChangedListener.onTimeChanged(mDelegator,
+ getCurrentHour(), getCurrentMinute());
}
}
@@ -559,19 +525,34 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
* Used to save / restore state of time picker
*/
private static class SavedState extends View.BaseSavedState {
+
private final int mHour;
private final int mMinute;
-
- private SavedState(Parcelable superState, int hour, int minute) {
+ private final boolean mIs24HourMode;
+ private final boolean mInKbMode;
+ private final ArrayList<Integer> mTypedTimes;
+ private final int mCurrentItemShowing;
+
+ private SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode,
+ boolean isKbMode, ArrayList<Integer> typedTimes,
+ int currentItemShowing) {
super(superState);
mHour = hour;
mMinute = minute;
+ mIs24HourMode = is24HourMode;
+ mInKbMode = isKbMode;
+ mTypedTimes = typedTimes;
+ mCurrentItemShowing = currentItemShowing;
}
private SavedState(Parcel in) {
super(in);
mHour = in.readInt();
mMinute = in.readInt();
+ mIs24HourMode = (in.readInt() == 1);
+ mInKbMode = (in.readInt() == 1);
+ mTypedTimes = in.readArrayList(getClass().getClassLoader());
+ mCurrentItemShowing = in.readInt();
}
public int getHour() {
@@ -582,11 +563,31 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
return mMinute;
}
+ public boolean is24HourMode() {
+ return mIs24HourMode;
+ }
+
+ public boolean inKbMode() {
+ return mInKbMode;
+ }
+
+ public ArrayList<Integer> getTypesTimes() {
+ return mTypedTimes;
+ }
+
+ public int getCurrentItemShowing() {
+ return mCurrentItemShowing;
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mHour);
dest.writeInt(mMinute);
+ dest.writeInt(mIs24HourMode ? 1 : 0);
+ dest.writeInt(mInKbMode ? 1 : 0);
+ dest.writeList(mTypedTimes);
+ dest.writeInt(mCurrentItemShowing);
}
@SuppressWarnings({"unused", "hiding"})
@@ -601,11 +602,707 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
};
}
- public static String[] getAmPmStrings(Context context) {
- String[] result = new String[2];
- LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale);
- result[0] = d.amPm[0].length() > 2 ? d.narrowAm : d.amPm[0];
- result[1] = d.amPm[1].length() > 2 ? d.narrowPm : d.amPm[1];
- return result;
+ private void tryVibrate() {
+ mDelegator.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
+ }
+
+ private void updateAmPmLabelStates(int amOrPm) {
+ final boolean isAm = amOrPm == AM;
+ mAmLabel.setChecked(isAm);
+ mAmLabel.setAlpha(isAm ? 1 : mDisabledAlpha);
+
+ final boolean isPm = amOrPm == PM;
+ mPmLabel.setChecked(isPm);
+ mPmLabel.setAlpha(isPm ? 1 : mDisabledAlpha);
+ }
+
+ /**
+ * Called by the picker for updating the header display.
+ */
+ @Override
+ public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) {
+ switch (pickerIndex) {
+ case HOUR_INDEX:
+ if (mAllowAutoAdvance && autoAdvance) {
+ updateHeaderHour(newValue, false);
+ setCurrentItemShowing(MINUTE_INDEX, true, false);
+ mDelegator.announceForAccessibility(newValue + ". " + mSelectMinutes);
+ } else {
+ updateHeaderHour(newValue, true);
+ }
+ break;
+ case MINUTE_INDEX:
+ updateHeaderMinute(newValue, true);
+ break;
+ case AMPM_INDEX:
+ updateAmPmLabelStates(newValue);
+ break;
+ case ENABLE_PICKER_INDEX:
+ if (!isTypedTimeFullyLegal()) {
+ mTypedTimes.clear();
+ }
+ finishKbMode();
+ break;
+ }
+
+ if (mOnTimeChangedListener != null) {
+ mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(), getCurrentMinute());
+ }
+ }
+
+ private void updateHeaderHour(int value, boolean announce) {
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+ (mIs24HourView) ? "Hm" : "hm");
+ final int lengthPattern = bestDateTimePattern.length();
+ boolean hourWithTwoDigit = false;
+ char hourFormat = '\0';
+ // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save
+ // the hour format that we found.
+ for (int i = 0; i < lengthPattern; i++) {
+ final char c = bestDateTimePattern.charAt(i);
+ if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
+ hourFormat = c;
+ if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
+ hourWithTwoDigit = true;
+ }
+ break;
+ }
+ }
+ final String format;
+ if (hourWithTwoDigit) {
+ format = "%02d";
+ } else {
+ format = "%d";
+ }
+ if (mIs24HourView) {
+ // 'k' means 1-24 hour
+ if (hourFormat == 'k' && value == 0) {
+ value = 24;
+ }
+ } else {
+ // 'K' means 0-11 hour
+ value = modulo12(value, hourFormat == 'K');
+ }
+ CharSequence text = String.format(format, value);
+ mHourView.setText(text);
+ if (announce) {
+ tryAnnounceForAccessibility(text, true);
+ }
+ }
+
+ private void tryAnnounceForAccessibility(CharSequence text, boolean isHour) {
+ if (mLastAnnouncedIsHour != isHour || !text.equals(mLastAnnouncedText)) {
+ // TODO: Find a better solution, potentially live regions?
+ mDelegator.announceForAccessibility(text);
+ mLastAnnouncedText = text;
+ mLastAnnouncedIsHour = isHour;
+ }
+ }
+
+ private static int modulo12(int n, boolean startWithZero) {
+ int value = n % 12;
+ if (value == 0 && !startWithZero) {
+ value = 12;
+ }
+ return value;
+ }
+
+ /**
+ * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":".
+ *
+ * See http://unicode.org/cldr/trac/browser/trunk/common/main
+ *
+ * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the
+ * separator as the character which is just after the hour marker in the returned pattern.
+ */
+ private void updateHeaderSeparator() {
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+ (mIs24HourView) ? "Hm" : "hm");
+ final String separatorText;
+ // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats
+ final char[] hourFormats = {'H', 'h', 'K', 'k'};
+ int hIndex = lastIndexOfAny(bestDateTimePattern, hourFormats);
+ if (hIndex == -1) {
+ // Default case
+ separatorText = ":";
+ } else {
+ separatorText = Character.toString(bestDateTimePattern.charAt(hIndex + 1));
+ }
+ mSeparatorView.setText(separatorText);
+ }
+
+ static private int lastIndexOfAny(String str, char[] any) {
+ final int lengthAny = any.length;
+ if (lengthAny > 0) {
+ for (int i = str.length() - 1; i >= 0; i--) {
+ char c = str.charAt(i);
+ for (int j = 0; j < lengthAny; j++) {
+ if (c == any[j]) {
+ return i;
+ }
+ }
+ }
+ }
+ return -1;
+ }
+
+ private void updateHeaderMinute(int value, boolean announceForAccessibility) {
+ if (value == 60) {
+ value = 0;
+ }
+ final CharSequence text = String.format(mCurrentLocale, "%02d", value);
+ mMinuteView.setText(text);
+ if (announceForAccessibility) {
+ tryAnnounceForAccessibility(text, false);
+ }
}
+
+ /**
+ * Show either Hours or Minutes.
+ */
+ private void setCurrentItemShowing(int index, boolean animateCircle, boolean announce) {
+ mRadialTimePickerView.setCurrentItemShowing(index, animateCircle);
+
+ if (index == HOUR_INDEX) {
+ if (announce) {
+ mDelegator.announceForAccessibility(mSelectHours);
+ }
+ } else {
+ if (announce) {
+ mDelegator.announceForAccessibility(mSelectMinutes);
+ }
+ }
+
+ mHourView.setSelected(index == HOUR_INDEX);
+ mMinuteView.setSelected(index == MINUTE_INDEX);
+ }
+
+ private void setAmOrPm(int amOrPm) {
+ updateAmPmLabelStates(amOrPm);
+ mRadialTimePickerView.setAmOrPm(amOrPm);
+ }
+
+ /**
+ * For keyboard mode, processes key events.
+ *
+ * @param keyCode the pressed key.
+ *
+ * @return true if the key was successfully processed, false otherwise.
+ */
+ private boolean processKeyUp(int keyCode) {
+ if (keyCode == KeyEvent.KEYCODE_DEL) {
+ if (mInKbMode) {
+ if (!mTypedTimes.isEmpty()) {
+ int deleted = deleteLastTypedKey();
+ String deletedKeyStr;
+ if (deleted == getAmOrPmKeyCode(AM)) {
+ deletedKeyStr = mAmText;
+ } else if (deleted == getAmOrPmKeyCode(PM)) {
+ deletedKeyStr = mPmText;
+ } else {
+ deletedKeyStr = String.format("%d", getValFromKeyCode(deleted));
+ }
+ mDelegator.announceForAccessibility(
+ String.format(mDeletedKeyFormat, deletedKeyStr));
+ updateDisplay(true);
+ }
+ }
+ } else if (keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1
+ || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3
+ || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5
+ || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
+ || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9
+ || (!mIs24HourView &&
+ (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) {
+ if (!mInKbMode) {
+ if (mRadialTimePickerView == null) {
+ // Something's wrong, because time picker should definitely not be null.
+ Log.e(TAG, "Unable to initiate keyboard mode, TimePicker was null.");
+ return true;
+ }
+ mTypedTimes.clear();
+ tryStartingKbMode(keyCode);
+ return true;
+ }
+ // We're already in keyboard mode.
+ if (addKeyIfLegal(keyCode)) {
+ updateDisplay(false);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Try to start keyboard mode with the specified key.
+ *
+ * @param keyCode The key to use as the first press. Keyboard mode will not be started if the
+ * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting
+ * key.
+ */
+ private void tryStartingKbMode(int keyCode) {
+ if (keyCode == -1 || addKeyIfLegal(keyCode)) {
+ mInKbMode = true;
+ onValidationChanged(false);
+ updateDisplay(false);
+ mRadialTimePickerView.setInputEnabled(false);
+ }
+ }
+
+ private boolean addKeyIfLegal(int keyCode) {
+ // If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode,
+ // we'll need to see if AM/PM have been typed.
+ if ((mIs24HourView && mTypedTimes.size() == 4) ||
+ (!mIs24HourView && isTypedTimeFullyLegal())) {
+ return false;
+ }
+
+ mTypedTimes.add(keyCode);
+ if (!isTypedTimeLegalSoFar()) {
+ deleteLastTypedKey();
+ return false;
+ }
+
+ int val = getValFromKeyCode(keyCode);
+ mDelegator.announceForAccessibility(String.format("%d", val));
+ // Automatically fill in 0's if AM or PM was legally entered.
+ if (isTypedTimeFullyLegal()) {
+ if (!mIs24HourView && mTypedTimes.size() <= 3) {
+ mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
+ mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
+ }
+ onValidationChanged(true);
+ }
+
+ return true;
+ }
+
+ /**
+ * Traverse the tree to see if the keys that have been typed so far are legal as is,
+ * or may become legal as more keys are typed (excluding backspace).
+ */
+ private boolean isTypedTimeLegalSoFar() {
+ Node node = mLegalTimesTree;
+ for (int keyCode : mTypedTimes) {
+ node = node.canReach(keyCode);
+ if (node == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Check if the time that has been typed so far is completely legal, as is.
+ */
+ private boolean isTypedTimeFullyLegal() {
+ if (mIs24HourView) {
+ // For 24-hour mode, the time is legal if the hours and minutes are each legal. Note:
+ // getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode.
+ int[] values = getEnteredTime(null);
+ return (values[0] >= 0 && values[1] >= 0 && values[1] < 60);
+ } else {
+ // For AM/PM mode, the time is legal if it contains an AM or PM, as those can only be
+ // legally added at specific times based on the tree's algorithm.
+ return (mTypedTimes.contains(getAmOrPmKeyCode(AM)) ||
+ mTypedTimes.contains(getAmOrPmKeyCode(PM)));
+ }
+ }
+
+ private int deleteLastTypedKey() {
+ int deleted = mTypedTimes.remove(mTypedTimes.size() - 1);
+ if (!isTypedTimeFullyLegal()) {
+ onValidationChanged(false);
+ }
+ return deleted;
+ }
+
+ /**
+ * Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time.
+ */
+ private void finishKbMode() {
+ mInKbMode = false;
+ if (!mTypedTimes.isEmpty()) {
+ int values[] = getEnteredTime(null);
+ mRadialTimePickerView.setCurrentHour(values[0]);
+ mRadialTimePickerView.setCurrentMinute(values[1]);
+ if (!mIs24HourView) {
+ mRadialTimePickerView.setAmOrPm(values[2]);
+ }
+ mTypedTimes.clear();
+ }
+ updateDisplay(false);
+ mRadialTimePickerView.setInputEnabled(true);
+ }
+
+ /**
+ * Update the hours, minutes, and AM/PM displays with the typed times. If the typedTimes is
+ * empty, either show an empty display (filled with the placeholder text), or update from the
+ * timepicker's values.
+ *
+ * @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text.
+ * Otherwise, revert to the timepicker's values.
+ */
+ private void updateDisplay(boolean allowEmptyDisplay) {
+ if (!allowEmptyDisplay && mTypedTimes.isEmpty()) {
+ int hour = mRadialTimePickerView.getCurrentHour();
+ int minute = mRadialTimePickerView.getCurrentMinute();
+ updateHeaderHour(hour, false);
+ updateHeaderMinute(minute, false);
+ if (!mIs24HourView) {
+ updateAmPmLabelStates(hour < 12 ? AM : PM);
+ }
+ setCurrentItemShowing(mRadialTimePickerView.getCurrentItemShowing(), true, true);
+ onValidationChanged(true);
+ } else {
+ boolean[] enteredZeros = {false, false};
+ int[] values = getEnteredTime(enteredZeros);
+ String hourFormat = enteredZeros[0] ? "%02d" : "%2d";
+ String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d";
+ String hourStr = (values[0] == -1) ? mDoublePlaceholderText :
+ String.format(hourFormat, values[0]).replace(' ', mPlaceholderText);
+ String minuteStr = (values[1] == -1) ? mDoublePlaceholderText :
+ String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
+ mHourView.setText(hourStr);
+ mHourView.setSelected(false);
+ mMinuteView.setText(minuteStr);
+ mMinuteView.setSelected(false);
+ if (!mIs24HourView) {
+ updateAmPmLabelStates(values[2]);
+ }
+ }
+ }
+
+ private int getValFromKeyCode(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_0:
+ return 0;
+ case KeyEvent.KEYCODE_1:
+ return 1;
+ case KeyEvent.KEYCODE_2:
+ return 2;
+ case KeyEvent.KEYCODE_3:
+ return 3;
+ case KeyEvent.KEYCODE_4:
+ return 4;
+ case KeyEvent.KEYCODE_5:
+ return 5;
+ case KeyEvent.KEYCODE_6:
+ return 6;
+ case KeyEvent.KEYCODE_7:
+ return 7;
+ case KeyEvent.KEYCODE_8:
+ return 8;
+ case KeyEvent.KEYCODE_9:
+ return 9;
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * Get the currently-entered time, as integer values of the hours and minutes typed.
+ *
+ * @param enteredZeros A size-2 boolean array, which the caller should initialize, and which
+ * may then be used for the caller to know whether zeros had been explicitly entered as either
+ * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's.
+ *
+ * @return A size-3 int array. The first value will be the hours, the second value will be the
+ * minutes, and the third will be either AM or PM.
+ */
+ private int[] getEnteredTime(boolean[] enteredZeros) {
+ int amOrPm = -1;
+ int startIndex = 1;
+ if (!mIs24HourView && isTypedTimeFullyLegal()) {
+ int keyCode = mTypedTimes.get(mTypedTimes.size() - 1);
+ if (keyCode == getAmOrPmKeyCode(AM)) {
+ amOrPm = AM;
+ } else if (keyCode == getAmOrPmKeyCode(PM)){
+ amOrPm = PM;
+ }
+ startIndex = 2;
+ }
+ int minute = -1;
+ int hour = -1;
+ for (int i = startIndex; i <= mTypedTimes.size(); i++) {
+ int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i));
+ if (i == startIndex) {
+ minute = val;
+ } else if (i == startIndex+1) {
+ minute += 10 * val;
+ if (enteredZeros != null && val == 0) {
+ enteredZeros[1] = true;
+ }
+ } else if (i == startIndex+2) {
+ hour = val;
+ } else if (i == startIndex+3) {
+ hour += 10 * val;
+ if (enteredZeros != null && val == 0) {
+ enteredZeros[0] = true;
+ }
+ }
+ }
+
+ return new int[] { hour, minute, amOrPm };
+ }
+
+ /**
+ * Get the keycode value for AM and PM in the current language.
+ */
+ private int getAmOrPmKeyCode(int amOrPm) {
+ // Cache the codes.
+ if (mAmKeyCode == -1 || mPmKeyCode == -1) {
+ // Find the first character in the AM/PM text that is unique.
+ final KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ final CharSequence amText = mAmText.toLowerCase(mCurrentLocale);
+ final CharSequence pmText = mPmText.toLowerCase(mCurrentLocale);
+ final int N = Math.min(amText.length(), pmText.length());
+ for (int i = 0; i < N; i++) {
+ final char amChar = amText.charAt(i);
+ final char pmChar = pmText.charAt(i);
+ if (amChar != pmChar) {
+ // There should be 4 events: a down and up for both AM and PM.
+ final KeyEvent[] events = kcm.getEvents(new char[] { amChar, pmChar });
+ if (events != null && events.length == 4) {
+ mAmKeyCode = events[0].getKeyCode();
+ mPmKeyCode = events[2].getKeyCode();
+ } else {
+ Log.e(TAG, "Unable to find keycodes for AM and PM.");
+ }
+ break;
+ }
+ }
+ }
+
+ if (amOrPm == AM) {
+ return mAmKeyCode;
+ } else if (amOrPm == PM) {
+ return mPmKeyCode;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Create a tree for deciding what keys can legally be typed.
+ */
+ private void generateLegalTimesTree() {
+ // Create a quick cache of numbers to their keycodes.
+ final int k0 = KeyEvent.KEYCODE_0;
+ final int k1 = KeyEvent.KEYCODE_1;
+ final int k2 = KeyEvent.KEYCODE_2;
+ final int k3 = KeyEvent.KEYCODE_3;
+ final int k4 = KeyEvent.KEYCODE_4;
+ final int k5 = KeyEvent.KEYCODE_5;
+ final int k6 = KeyEvent.KEYCODE_6;
+ final int k7 = KeyEvent.KEYCODE_7;
+ final int k8 = KeyEvent.KEYCODE_8;
+ final int k9 = KeyEvent.KEYCODE_9;
+
+ // The root of the tree doesn't contain any numbers.
+ mLegalTimesTree = new Node();
+ if (mIs24HourView) {
+ // We'll be re-using these nodes, so we'll save them.
+ Node minuteFirstDigit = new Node(k0, k1, k2, k3, k4, k5);
+ Node minuteSecondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ // The first digit must be followed by the second digit.
+ minuteFirstDigit.addChild(minuteSecondDigit);
+
+ // The first digit may be 0-1.
+ Node firstDigit = new Node(k0, k1);
+ mLegalTimesTree.addChild(firstDigit);
+
+ // When the first digit is 0-1, the second digit may be 0-5.
+ Node secondDigit = new Node(k0, k1, k2, k3, k4, k5);
+ firstDigit.addChild(secondDigit);
+ // We may now be followed by the first minute digit. E.g. 00:09, 15:58.
+ secondDigit.addChild(minuteFirstDigit);
+
+ // When the first digit is 0-1, and the second digit is 0-5, the third digit may be 6-9.
+ Node thirdDigit = new Node(k6, k7, k8, k9);
+ // The time must now be finished. E.g. 0:55, 1:08.
+ secondDigit.addChild(thirdDigit);
+
+ // When the first digit is 0-1, the second digit may be 6-9.
+ secondDigit = new Node(k6, k7, k8, k9);
+ firstDigit.addChild(secondDigit);
+ // We must now be followed by the first minute digit. E.g. 06:50, 18:20.
+ secondDigit.addChild(minuteFirstDigit);
+
+ // The first digit may be 2.
+ firstDigit = new Node(k2);
+ mLegalTimesTree.addChild(firstDigit);
+
+ // When the first digit is 2, the second digit may be 0-3.
+ secondDigit = new Node(k0, k1, k2, k3);
+ firstDigit.addChild(secondDigit);
+ // We must now be followed by the first minute digit. E.g. 20:50, 23:09.
+ secondDigit.addChild(minuteFirstDigit);
+
+ // When the first digit is 2, the second digit may be 4-5.
+ secondDigit = new Node(k4, k5);
+ firstDigit.addChild(secondDigit);
+ // We must now be followd by the last minute digit. E.g. 2:40, 2:53.
+ secondDigit.addChild(minuteSecondDigit);
+
+ // The first digit may be 3-9.
+ firstDigit = new Node(k3, k4, k5, k6, k7, k8, k9);
+ mLegalTimesTree.addChild(firstDigit);
+ // We must now be followed by the first minute digit. E.g. 3:57, 8:12.
+ firstDigit.addChild(minuteFirstDigit);
+ } else {
+ // We'll need to use the AM/PM node a lot.
+ // Set up AM and PM to respond to "a" and "p".
+ Node ampm = new Node(getAmOrPmKeyCode(AM), getAmOrPmKeyCode(PM));
+
+ // The first hour digit may be 1.
+ Node firstDigit = new Node(k1);
+ mLegalTimesTree.addChild(firstDigit);
+ // We'll allow quick input of on-the-hour times. E.g. 1pm.
+ firstDigit.addChild(ampm);
+
+ // When the first digit is 1, the second digit may be 0-2.
+ Node secondDigit = new Node(k0, k1, k2);
+ firstDigit.addChild(secondDigit);
+ // Also for quick input of on-the-hour times. E.g. 10pm, 12am.
+ secondDigit.addChild(ampm);
+
+ // When the first digit is 1, and the second digit is 0-2, the third digit may be 0-5.
+ Node thirdDigit = new Node(k0, k1, k2, k3, k4, k5);
+ secondDigit.addChild(thirdDigit);
+ // The time may be finished now. E.g. 1:02pm, 1:25am.
+ thirdDigit.addChild(ampm);
+
+ // When the first digit is 1, the second digit is 0-2, and the third digit is 0-5,
+ // the fourth digit may be 0-9.
+ Node fourthDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ thirdDigit.addChild(fourthDigit);
+ // The time must be finished now. E.g. 10:49am, 12:40pm.
+ fourthDigit.addChild(ampm);
+
+ // When the first digit is 1, and the second digit is 0-2, the third digit may be 6-9.
+ thirdDigit = new Node(k6, k7, k8, k9);
+ secondDigit.addChild(thirdDigit);
+ // The time must be finished now. E.g. 1:08am, 1:26pm.
+ thirdDigit.addChild(ampm);
+
+ // When the first digit is 1, the second digit may be 3-5.
+ secondDigit = new Node(k3, k4, k5);
+ firstDigit.addChild(secondDigit);
+
+ // When the first digit is 1, and the second digit is 3-5, the third digit may be 0-9.
+ thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ secondDigit.addChild(thirdDigit);
+ // The time must be finished now. E.g. 1:39am, 1:50pm.
+ thirdDigit.addChild(ampm);
+
+ // The hour digit may be 2-9.
+ firstDigit = new Node(k2, k3, k4, k5, k6, k7, k8, k9);
+ mLegalTimesTree.addChild(firstDigit);
+ // We'll allow quick input of on-the-hour-times. E.g. 2am, 5pm.
+ firstDigit.addChild(ampm);
+
+ // When the first digit is 2-9, the second digit may be 0-5.
+ secondDigit = new Node(k0, k1, k2, k3, k4, k5);
+ firstDigit.addChild(secondDigit);
+
+ // When the first digit is 2-9, and the second digit is 0-5, the third digit may be 0-9.
+ thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ secondDigit.addChild(thirdDigit);
+ // The time must be finished now. E.g. 2:57am, 9:30pm.
+ thirdDigit.addChild(ampm);
+ }
+ }
+
+ /**
+ * Simple node class to be used for traversal to check for legal times.
+ * mLegalKeys represents the keys that can be typed to get to the node.
+ * mChildren are the children that can be reached from this node.
+ */
+ private class Node {
+ private int[] mLegalKeys;
+ private ArrayList<Node> mChildren;
+
+ public Node(int... legalKeys) {
+ mLegalKeys = legalKeys;
+ mChildren = new ArrayList<Node>();
+ }
+
+ public void addChild(Node child) {
+ mChildren.add(child);
+ }
+
+ public boolean containsKey(int key) {
+ for (int i = 0; i < mLegalKeys.length; i++) {
+ if (mLegalKeys[i] == key) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Node canReach(int key) {
+ if (mChildren == null) {
+ return null;
+ }
+ for (Node child : mChildren) {
+ if (child.containsKey(key)) {
+ return child;
+ }
+ }
+ return null;
+ }
+ }
+
+ private final View.OnClickListener mClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ final int amOrPm;
+ switch (v.getId()) {
+ case R.id.am_label:
+ setAmOrPm(AM);
+ break;
+ case R.id.pm_label:
+ setAmOrPm(PM);
+ break;
+ case R.id.hours:
+ setCurrentItemShowing(HOUR_INDEX, true, true);
+ break;
+ case R.id.minutes:
+ setCurrentItemShowing(MINUTE_INDEX, true, true);
+ break;
+ default:
+ // Failed to handle this click, don't vibrate.
+ return;
+ }
+
+ tryVibrate();
+ }
+ };
+
+ private final View.OnKeyListener mKeyListener = new View.OnKeyListener() {
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ return processKeyUp(keyCode);
+ }
+ return false;
+ }
+ };
+
+ private final View.OnFocusChangeListener mFocusListener = new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (!hasFocus && mInKbMode && isTypedTimeFullyLegal()) {
+ finishKbMode();
+
+ if (mOnTimeChangedListener != null) {
+ mOnTimeChangedListener.onTimeChanged(mDelegator,
+ mRadialTimePickerView.getCurrentHour(),
+ mRadialTimePickerView.getCurrentMinute());
+ }
+ }
+ }
+ };
}
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index 73e05e8..e162f4a 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -17,380 +17,365 @@
package android.widget;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
-import android.text.TextUtils;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.AttributeSet;
-import android.util.Log;
-import android.view.HapticFeedbackConstants;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
-
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
import com.android.internal.R;
-import java.util.ArrayList;
+import java.text.DateFormatSymbols;
import java.util.Calendar;
import java.util.Locale;
-/**
- * A view for selecting the time of day, in either 24 hour or AM/PM mode.
- */
-class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate implements
- RadialTimePickerView.OnValueSelectedListener {
-
- private static final String TAG = "TimePickerDelegate";
-
- // Index used by RadialPickerLayout
- private static final int HOUR_INDEX = 0;
- private static final int MINUTE_INDEX = 1;
-
- // NOT a real index for the purpose of what's showing.
- private static final int AMPM_INDEX = 2;
+import libcore.icu.LocaleData;
- // Also NOT a real index, just used for keyboard mode.
- private static final int ENABLE_PICKER_INDEX = 3;
-
- private static final int AM = 0;
- private static final int PM = 1;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
+/**
+ * A delegate implementing the basic spinner-based TimePicker.
+ */
+class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
private static final boolean DEFAULT_ENABLED_STATE = true;
- private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
-
private static final int HOURS_IN_HALF_DAY = 12;
- private View mHeaderView;
- private TextView mHourView;
- private TextView mMinuteView;
- private TextView mAmPmTextView;
- private RadialTimePickerView mRadialTimePickerView;
- private TextView mSeparatorView;
+ // state
+ private boolean mIs24HourView;
+ private boolean mIsAm;
- private String mAmText;
- private String mPmText;
+ // ui components
+ private final NumberPicker mHourSpinner;
+ private final NumberPicker mMinuteSpinner;
+ private final NumberPicker mAmPmSpinner;
+ private final EditText mHourSpinnerInput;
+ private final EditText mMinuteSpinnerInput;
+ private final EditText mAmPmSpinnerInput;
+ private final TextView mDivider;
- private boolean mAllowAutoAdvance;
- private int mInitialHourOfDay;
- private int mInitialMinute;
- private boolean mIs24HourView;
+ // Note that the legacy implementation of the TimePicker is
+ // using a button for toggling between AM/PM while the new
+ // version uses a NumberPicker spinner. Therefore the code
+ // accommodates these two cases to be backwards compatible.
+ private final Button mAmPmButton;
- // For hardware IME input.
- private char mPlaceholderText;
- private String mDoublePlaceholderText;
- private String mDeletedKeyFormat;
- private boolean mInKbMode;
- private ArrayList<Integer> mTypedTimes = new ArrayList<Integer>();
- private Node mLegalTimesTree;
- private int mAmKeyCode;
- private int mPmKeyCode;
-
- // Accessibility strings.
- private String mHourPickerDescription;
- private String mSelectHours;
- private String mMinutePickerDescription;
- private String mSelectMinutes;
+ private final String[] mAmPmStrings;
+ private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
private Calendar mTempCalendar;
+ private boolean mHourWithTwoDigit;
+ private char mHourFormat;
public TimePickerSpinnerDelegate(TimePicker delegator, Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(delegator, context);
// process style attributes
- final TypedArray a = mContext.obtainStyledAttributes(attrs,
- R.styleable.TimePicker, defStyleAttr, defStyleRes);
- final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- final Resources res = mContext.getResources();
-
- mHourPickerDescription = res.getString(R.string.hour_picker_description);
- mSelectHours = res.getString(R.string.select_hours);
- mMinutePickerDescription = res.getString(R.string.minute_picker_description);
- mSelectMinutes = res.getString(R.string.select_minutes);
-
- String[] amPmStrings = TimePickerClockDelegate.getAmPmStrings(context);
- mAmText = amPmStrings[0];
- mPmText = amPmStrings[1];
-
- final int layoutResourceId = a.getResourceId(R.styleable.TimePicker_internalLayout,
- R.layout.time_picker_holo);
- final View mainView = inflater.inflate(layoutResourceId, null);
- mDelegator.addView(mainView);
-
- mHourView = (TextView) mainView.findViewById(R.id.hours);
- mSeparatorView = (TextView) mainView.findViewById(R.id.separator);
- mMinuteView = (TextView) mainView.findViewById(R.id.minutes);
- mAmPmTextView = (TextView) mainView.findViewById(R.id.ampm_label);
-
- // Set up text appearances from style.
- final int headerTimeTextAppearance = a.getResourceId(
- R.styleable.TimePicker_headerTimeTextAppearance, 0);
- if (headerTimeTextAppearance != 0) {
- mHourView.setTextAppearance(context, headerTimeTextAppearance);
- mSeparatorView.setTextAppearance(context, headerTimeTextAppearance);
- mMinuteView.setTextAppearance(context, headerTimeTextAppearance);
- }
+ final TypedArray a = mContext.obtainStyledAttributes(
+ attrs, R.styleable.TimePicker, defStyleAttr, defStyleRes);
+ final int layoutResourceId = a.getResourceId(
+ R.styleable.TimePicker_legacyLayout, R.layout.time_picker_legacy);
+ a.recycle();
- final int headerSelectedTextColor = a.getColor(
- R.styleable.TimePicker_headerSelectedTextColor,
- res.getColor(R.color.timepicker_default_selector_color_material));
- mHourView.setTextColor(ColorStateList.addFirstIfMissing(mHourView.getTextColors(),
- R.attr.state_selected, headerSelectedTextColor));
- mMinuteView.setTextColor(ColorStateList.addFirstIfMissing(mMinuteView.getTextColors(),
- R.attr.state_selected, headerSelectedTextColor));
-
- final int headerAmPmTextAppearance = a.getResourceId(
- R.styleable.TimePicker_headerAmPmTextAppearance, 0);
- if (headerAmPmTextAppearance != 0) {
- mAmPmTextView.setTextAppearance(context, headerAmPmTextAppearance);
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ inflater.inflate(layoutResourceId, mDelegator, true);
+
+ // hour
+ mHourSpinner = (NumberPicker) delegator.findViewById(R.id.hour);
+ mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
+ public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
+ updateInputState();
+ if (!is24HourView()) {
+ if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) ||
+ (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) {
+ mIsAm = !mIsAm;
+ updateAmPmControl();
+ }
+ }
+ onTimeChanged();
+ }
+ });
+ mHourSpinnerInput = (EditText) mHourSpinner.findViewById(R.id.numberpicker_input);
+ mHourSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
+
+ // divider (only for the new widget style)
+ mDivider = (TextView) mDelegator.findViewById(R.id.divider);
+ if (mDivider != null) {
+ setDividerText();
+ }
+
+ // minute
+ mMinuteSpinner = (NumberPicker) mDelegator.findViewById(R.id.minute);
+ mMinuteSpinner.setMinValue(0);
+ mMinuteSpinner.setMaxValue(59);
+ mMinuteSpinner.setOnLongPressUpdateInterval(100);
+ mMinuteSpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
+ mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
+ public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
+ updateInputState();
+ int minValue = mMinuteSpinner.getMinValue();
+ int maxValue = mMinuteSpinner.getMaxValue();
+ if (oldVal == maxValue && newVal == minValue) {
+ int newHour = mHourSpinner.getValue() + 1;
+ if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) {
+ mIsAm = !mIsAm;
+ updateAmPmControl();
+ }
+ mHourSpinner.setValue(newHour);
+ } else if (oldVal == minValue && newVal == maxValue) {
+ int newHour = mHourSpinner.getValue() - 1;
+ if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) {
+ mIsAm = !mIsAm;
+ updateAmPmControl();
+ }
+ mHourSpinner.setValue(newHour);
+ }
+ onTimeChanged();
+ }
+ });
+ mMinuteSpinnerInput = (EditText) mMinuteSpinner.findViewById(R.id.numberpicker_input);
+ mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
+
+ // Get the localized am/pm strings and use them in the spinner.
+ mAmPmStrings = getAmPmStrings(context);
+
+ // am/pm
+ final View amPmView = mDelegator.findViewById(R.id.amPm);
+ if (amPmView instanceof Button) {
+ mAmPmSpinner = null;
+ mAmPmSpinnerInput = null;
+ mAmPmButton = (Button) amPmView;
+ mAmPmButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View button) {
+ button.requestFocus();
+ mIsAm = !mIsAm;
+ updateAmPmControl();
+ onTimeChanged();
+ }
+ });
+ } else {
+ mAmPmButton = null;
+ mAmPmSpinner = (NumberPicker) amPmView;
+ mAmPmSpinner.setMinValue(0);
+ mAmPmSpinner.setMaxValue(1);
+ mAmPmSpinner.setDisplayedValues(mAmPmStrings);
+ mAmPmSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
+ public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
+ updateInputState();
+ picker.requestFocus();
+ mIsAm = !mIsAm;
+ updateAmPmControl();
+ onTimeChanged();
+ }
+ });
+ mAmPmSpinnerInput = (EditText) mAmPmSpinner.findViewById(R.id.numberpicker_input);
+ mAmPmSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
+ }
+
+ if (isAmPmAtStart()) {
+ // Move the am/pm view to the beginning
+ ViewGroup amPmParent = (ViewGroup) delegator.findViewById(R.id.timePickerLayout);
+ amPmParent.removeView(amPmView);
+ amPmParent.addView(amPmView, 0);
+ // Swap layout margins if needed. They may be not symmetrical (Old Standard Theme
+ // for example and not for Holo Theme)
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) amPmView.getLayoutParams();
+ final int startMargin = lp.getMarginStart();
+ final int endMargin = lp.getMarginEnd();
+ if (startMargin != endMargin) {
+ lp.setMarginStart(endMargin);
+ lp.setMarginEnd(startMargin);
+ }
}
- mHeaderView = mainView.findViewById(R.id.time_header);
- mHeaderView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground));
-
- a.recycle();
+ getHourFormatData();
- mRadialTimePickerView = (RadialTimePickerView) mainView.findViewById(
- R.id.radial_picker);
+ // update controls to initial state
+ updateHourControl();
+ updateMinuteControl();
+ updateAmPmControl();
- setupListeners();
+ // set to current time
+ setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
+ setCurrentMinute(mTempCalendar.get(Calendar.MINUTE));
- mAllowAutoAdvance = true;
+ if (!isEnabled()) {
+ setEnabled(false);
+ }
- // Set up for keyboard mode.
- mDoublePlaceholderText = res.getString(R.string.time_placeholder);
- mDeletedKeyFormat = res.getString(R.string.deleted_key);
- mPlaceholderText = mDoublePlaceholderText.charAt(0);
- mAmKeyCode = mPmKeyCode = -1;
- generateLegalTimesTree();
+ // set the content descriptions
+ setContentDescriptions();
- // Initialize with current time
- final Calendar calendar = Calendar.getInstance(mCurrentLocale);
- final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
- final int currentMinute = calendar.get(Calendar.MINUTE);
- initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX);
- }
-
- private void initialize(int hourOfDay, int minute, boolean is24HourView, int index) {
- mInitialHourOfDay = hourOfDay;
- mInitialMinute = minute;
- mIs24HourView = is24HourView;
- mInKbMode = false;
- updateUI(index);
+ // If not explicitly specified this view is important for accessibility.
+ if (mDelegator.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
- private void setupListeners() {
- mHeaderView.setOnKeyListener(mKeyListener);
- mHeaderView.setOnFocusChangeListener(mFocusListener);
- mHeaderView.setFocusable(true);
-
- mRadialTimePickerView.setOnValueSelectedListener(this);
-
- mHourView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- setCurrentItemShowing(HOUR_INDEX, true, true);
- tryVibrate();
- }
- });
- mMinuteView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- setCurrentItemShowing(MINUTE_INDEX, true, true);
- tryVibrate();
+ private void getHourFormatData() {
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+ (mIs24HourView) ? "Hm" : "hm");
+ final int lengthPattern = bestDateTimePattern.length();
+ mHourWithTwoDigit = false;
+ char hourFormat = '\0';
+ // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save
+ // the hour format that we found.
+ for (int i = 0; i < lengthPattern; i++) {
+ final char c = bestDateTimePattern.charAt(i);
+ if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
+ mHourFormat = c;
+ if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
+ mHourWithTwoDigit = true;
+ }
+ break;
}
- });
+ }
}
- private void updateUI(int index) {
- // Update RadialPicker values
- updateRadialPicker(index);
- // Enable or disable the AM/PM view.
- updateHeaderAmPm();
- // Update Hour and Minutes
- updateHeaderHour(mInitialHourOfDay, true);
- // Update time separator
- updateHeaderSeparator();
- // Update Minutes
- updateHeaderMinute(mInitialMinute);
- // Invalidate everything
- mDelegator.invalidate();
- }
+ private boolean isAmPmAtStart() {
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+ "hm" /* skeleton */);
- private void updateRadialPicker(int index) {
- mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24HourView);
- setCurrentItemShowing(index, false, true);
+ return bestDateTimePattern.startsWith("a");
}
- private int computeMaxWidthOfNumbers(int max) {
- TextView tempView = new TextView(mContext);
- tempView.setTextAppearance(mContext, R.style.TextAppearance_Material_TimePicker_TimeLabel);
- ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- tempView.setLayoutParams(lp);
- int maxWidth = 0;
- for (int minutes = 0; minutes < max; minutes++) {
- final String text = String.format("%02d", minutes);
- tempView.setText(text);
- tempView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
- maxWidth = Math.max(maxWidth, tempView.getMeasuredWidth());
+ /**
+ * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":".
+ *
+ * See http://unicode.org/cldr/trac/browser/trunk/common/main
+ *
+ * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the
+ * separator as the character which is just after the hour marker in the returned pattern.
+ */
+ private void setDividerText() {
+ final String skeleton = (mIs24HourView) ? "Hm" : "hm";
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+ skeleton);
+ final String separatorText;
+ int hourIndex = bestDateTimePattern.lastIndexOf('H');
+ if (hourIndex == -1) {
+ hourIndex = bestDateTimePattern.lastIndexOf('h');
}
- return maxWidth;
- }
-
- private void updateHeaderAmPm() {
- if (mIs24HourView) {
- mAmPmTextView.setVisibility(View.GONE);
+ if (hourIndex == -1) {
+ // Default case
+ separatorText = ":";
} else {
- mAmPmTextView.setVisibility(View.VISIBLE);
- final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
- "hm");
-
- boolean amPmOnLeft = bestDateTimePattern.startsWith("a");
- if (TextUtils.getLayoutDirectionFromLocale(mCurrentLocale) ==
- View.LAYOUT_DIRECTION_RTL) {
- amPmOnLeft = !amPmOnLeft;
- }
-
- RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)
- mAmPmTextView.getLayoutParams();
-
- if (amPmOnLeft) {
- layoutParams.rightMargin = computeMaxWidthOfNumbers(12 /* for hours */);
- layoutParams.removeRule(RelativeLayout.RIGHT_OF);
- layoutParams.addRule(RelativeLayout.LEFT_OF, R.id.separator);
+ int minuteIndex = bestDateTimePattern.indexOf('m', hourIndex + 1);
+ if (minuteIndex == -1) {
+ separatorText = Character.toString(bestDateTimePattern.charAt(hourIndex + 1));
} else {
- layoutParams.leftMargin = computeMaxWidthOfNumbers(60 /* for minutes */);
- layoutParams.removeRule(RelativeLayout.LEFT_OF);
- layoutParams.addRule(RelativeLayout.RIGHT_OF, R.id.separator);
+ separatorText = bestDateTimePattern.substring(hourIndex + 1, minuteIndex);
}
-
- updateAmPmDisplay(mInitialHourOfDay < 12 ? AM : PM);
- mAmPmTextView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- tryVibrate();
- int amOrPm = mRadialTimePickerView.getAmOrPm();
- if (amOrPm == AM) {
- amOrPm = PM;
- } else if (amOrPm == PM){
- amOrPm = AM;
- }
- updateAmPmDisplay(amOrPm);
- mRadialTimePickerView.setAmOrPm(amOrPm);
- }
- });
}
+ mDivider.setText(separatorText);
}
- /**
- * Set the current hour.
- */
@Override
public void setCurrentHour(Integer currentHour) {
- if (mInitialHourOfDay == currentHour) {
+ setCurrentHour(currentHour, true);
+ }
+
+ private void setCurrentHour(Integer currentHour, boolean notifyTimeChanged) {
+ // why was Integer used in the first place?
+ if (currentHour == null || currentHour == getCurrentHour()) {
return;
}
- mInitialHourOfDay = currentHour;
- updateHeaderHour(currentHour, true /* accessibility announce */);
- updateHeaderAmPm();
- mRadialTimePickerView.setCurrentHour(currentHour);
- mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM);
- mDelegator.invalidate();
- onTimeChanged();
+ if (!is24HourView()) {
+ // convert [0,23] ordinal to wall clock display
+ if (currentHour >= HOURS_IN_HALF_DAY) {
+ mIsAm = false;
+ if (currentHour > HOURS_IN_HALF_DAY) {
+ currentHour = currentHour - HOURS_IN_HALF_DAY;
+ }
+ } else {
+ mIsAm = true;
+ if (currentHour == 0) {
+ currentHour = HOURS_IN_HALF_DAY;
+ }
+ }
+ updateAmPmControl();
+ }
+ mHourSpinner.setValue(currentHour);
+ if (notifyTimeChanged) {
+ onTimeChanged();
+ }
}
- /**
- * @return The current hour in the range (0-23).
- */
@Override
public Integer getCurrentHour() {
- int currentHour = mRadialTimePickerView.getCurrentHour();
- if (mIs24HourView) {
+ int currentHour = mHourSpinner.getValue();
+ if (is24HourView()) {
return currentHour;
+ } else if (mIsAm) {
+ return currentHour % HOURS_IN_HALF_DAY;
} else {
- switch(mRadialTimePickerView.getAmOrPm()) {
- case PM:
- return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
- case AM:
- default:
- return currentHour % HOURS_IN_HALF_DAY;
- }
+ return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
}
}
- /**
- * Set the current minute (0-59).
- */
@Override
public void setCurrentMinute(Integer currentMinute) {
- if (mInitialMinute == currentMinute) {
+ if (currentMinute == getCurrentMinute()) {
return;
}
- mInitialMinute = currentMinute;
- updateHeaderMinute(currentMinute);
- mRadialTimePickerView.setCurrentMinute(currentMinute);
- mDelegator.invalidate();
+ mMinuteSpinner.setValue(currentMinute);
onTimeChanged();
}
- /**
- * @return The current minute.
- */
@Override
public Integer getCurrentMinute() {
- return mRadialTimePickerView.getCurrentMinute();
+ return mMinuteSpinner.getValue();
}
- /**
- * Set whether in 24 hour or AM/PM mode.
- *
- * @param is24HourView True = 24 hour mode. False = AM/PM.
- */
@Override
public void setIs24HourView(Boolean is24HourView) {
- if (is24HourView == mIs24HourView) {
+ if (mIs24HourView == is24HourView) {
return;
}
+ // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!!
+ int currentHour = getCurrentHour();
+ // Order is important here.
mIs24HourView = is24HourView;
- generateLegalTimesTree();
- int hour = mRadialTimePickerView.getCurrentHour();
- mInitialHourOfDay = hour;
- updateHeaderHour(hour, false /* no accessibility announce */);
- updateHeaderAmPm();
- updateRadialPicker(mRadialTimePickerView.getCurrentItemShowing());
- mDelegator.invalidate();
+ getHourFormatData();
+ updateHourControl();
+ // set value after spinner range is updated
+ setCurrentHour(currentHour, false);
+ updateMinuteControl();
+ updateAmPmControl();
}
- /**
- * @return true if this is in 24 hour view else false.
- */
@Override
public boolean is24HourView() {
return mIs24HourView;
}
@Override
- public void setOnTimeChangedListener(TimePicker.OnTimeChangedListener callback) {
- mOnTimeChangedListener = callback;
+ public void setOnTimeChangedListener(TimePicker.OnTimeChangedListener onTimeChangedListener) {
+ mOnTimeChangedListener = onTimeChangedListener;
}
@Override
public void setEnabled(boolean enabled) {
- mHourView.setEnabled(enabled);
- mMinuteView.setEnabled(enabled);
- mAmPmTextView.setEnabled(enabled);
- mRadialTimePickerView.setEnabled(enabled);
+ mMinuteSpinner.setEnabled(enabled);
+ if (mDivider != null) {
+ mDivider.setEnabled(enabled);
+ }
+ mHourSpinner.setEnabled(enabled);
+ if (mAmPmSpinner != null) {
+ mAmPmSpinner.setEnabled(enabled);
+ } else {
+ mAmPmButton.setEnabled(enabled);
+ }
mIsEnabled = enabled;
}
@@ -401,38 +386,24 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im
@Override
public int getBaseline() {
- // does not support baseline alignment
- return -1;
+ return mHourSpinner.getBaseline();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
- updateUI(mRadialTimePickerView.getCurrentItemShowing());
+ setCurrentLocale(newConfig.locale);
}
@Override
public Parcelable onSaveInstanceState(Parcelable superState) {
- return new SavedState(superState, getCurrentHour(), getCurrentMinute(),
- is24HourView(), inKbMode(), getTypedTimes(), getCurrentItemShowing());
+ return new SavedState(superState, getCurrentHour(), getCurrentMinute());
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
- setInKbMode(ss.inKbMode());
- setTypedTimes(ss.getTypesTimes());
- initialize(ss.getHour(), ss.getMinute(), ss.is24HourMode(), ss.getCurrentItemShowing());
- mRadialTimePickerView.invalidate();
- if (mInKbMode) {
- tryStartingKbMode(-1);
- mHourView.invalidate();
- }
- }
-
- @Override
- public void setCurrentLocale(Locale locale) {
- super.setCurrentLocale(locale);
- mTempCalendar = Calendar.getInstance(locale);
+ setCurrentHour(ss.getHour());
+ setCurrentMinute(ss.getMinute());
}
@Override
@@ -451,9 +422,9 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im
}
mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
- String selectedDate = DateUtils.formatDateTime(mContext,
+ String selectedDateUtterance = DateUtils.formatDateTime(mContext,
mTempCalendar.getTimeInMillis(), flags);
- event.getText().add(selectedDate);
+ event.getText().add(selectedDateUtterance);
}
@Override
@@ -466,48 +437,121 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im
info.setClassName(TimePicker.class.getName());
}
+ private void updateInputState() {
+ // Make sure that if the user changes the value and the IME is active
+ // for one of the inputs if this widget, the IME is closed. If the user
+ // changed the value via the IME and there is a next input the IME will
+ // be shown, otherwise the user chose another means of changing the
+ // value and having the IME up makes no sense.
+ InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
+ if (inputMethodManager != null) {
+ if (inputMethodManager.isActive(mHourSpinnerInput)) {
+ mHourSpinnerInput.clearFocus();
+ inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
+ } else if (inputMethodManager.isActive(mMinuteSpinnerInput)) {
+ mMinuteSpinnerInput.clearFocus();
+ inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
+ } else if (inputMethodManager.isActive(mAmPmSpinnerInput)) {
+ mAmPmSpinnerInput.clearFocus();
+ inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
+ }
+ }
+ }
+
+ private void updateAmPmControl() {
+ if (is24HourView()) {
+ if (mAmPmSpinner != null) {
+ mAmPmSpinner.setVisibility(View.GONE);
+ } else {
+ mAmPmButton.setVisibility(View.GONE);
+ }
+ } else {
+ int index = mIsAm ? Calendar.AM : Calendar.PM;
+ if (mAmPmSpinner != null) {
+ mAmPmSpinner.setValue(index);
+ mAmPmSpinner.setVisibility(View.VISIBLE);
+ } else {
+ mAmPmButton.setText(mAmPmStrings[index]);
+ mAmPmButton.setVisibility(View.VISIBLE);
+ }
+ }
+ mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ }
+
/**
- * Set whether in keyboard mode or not.
+ * Sets the current locale.
*
- * @param inKbMode True means in keyboard mode.
+ * @param locale The current locale.
*/
- private void setInKbMode(boolean inKbMode) {
- mInKbMode = inKbMode;
+ @Override
+ public void setCurrentLocale(Locale locale) {
+ super.setCurrentLocale(locale);
+ mTempCalendar = Calendar.getInstance(locale);
}
- /**
- * @return true if in keyboard mode
- */
- private boolean inKbMode() {
- return mInKbMode;
+ private void onTimeChanged() {
+ mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ if (mOnTimeChangedListener != null) {
+ mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(),
+ getCurrentMinute());
+ }
}
- private void setTypedTimes(ArrayList<Integer> typeTimes) {
- mTypedTimes = typeTimes;
+ private void updateHourControl() {
+ if (is24HourView()) {
+ // 'k' means 1-24 hour
+ if (mHourFormat == 'k') {
+ mHourSpinner.setMinValue(1);
+ mHourSpinner.setMaxValue(24);
+ } else {
+ mHourSpinner.setMinValue(0);
+ mHourSpinner.setMaxValue(23);
+ }
+ } else {
+ // 'K' means 0-11 hour
+ if (mHourFormat == 'K') {
+ mHourSpinner.setMinValue(0);
+ mHourSpinner.setMaxValue(11);
+ } else {
+ mHourSpinner.setMinValue(1);
+ mHourSpinner.setMaxValue(12);
+ }
+ }
+ mHourSpinner.setFormatter(mHourWithTwoDigit ? NumberPicker.getTwoDigitFormatter() : null);
}
- /**
- * @return an array of typed times
- */
- private ArrayList<Integer> getTypedTimes() {
- return mTypedTimes;
+ private void updateMinuteControl() {
+ if (is24HourView()) {
+ mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
+ } else {
+ mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
+ }
}
- /**
- * @return the index of the current item showing
- */
- private int getCurrentItemShowing() {
- return mRadialTimePickerView.getCurrentItemShowing();
+ private void setContentDescriptions() {
+ // Minute
+ trySetContentDescription(mMinuteSpinner, R.id.increment,
+ R.string.time_picker_increment_minute_button);
+ trySetContentDescription(mMinuteSpinner, R.id.decrement,
+ R.string.time_picker_decrement_minute_button);
+ // Hour
+ trySetContentDescription(mHourSpinner, R.id.increment,
+ R.string.time_picker_increment_hour_button);
+ trySetContentDescription(mHourSpinner, R.id.decrement,
+ R.string.time_picker_decrement_hour_button);
+ // AM/PM
+ if (mAmPmSpinner != null) {
+ trySetContentDescription(mAmPmSpinner, R.id.increment,
+ R.string.time_picker_increment_set_pm_button);
+ trySetContentDescription(mAmPmSpinner, R.id.decrement,
+ R.string.time_picker_decrement_set_am_button);
+ }
}
- /**
- * Propagate the time change
- */
- private void onTimeChanged() {
- mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
- if (mOnTimeChangedListener != null) {
- mOnTimeChangedListener.onTimeChanged(mDelegator,
- getCurrentHour(), getCurrentMinute());
+ private void trySetContentDescription(View root, int viewId, int contDescResId) {
+ View target = root.findViewById(viewId);
+ if (target != null) {
+ target.setContentDescription(mContext.getString(contDescResId));
}
}
@@ -515,34 +559,19 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im
* Used to save / restore state of time picker
*/
private static class SavedState extends View.BaseSavedState {
-
private final int mHour;
private final int mMinute;
- private final boolean mIs24HourMode;
- private final boolean mInKbMode;
- private final ArrayList<Integer> mTypedTimes;
- private final int mCurrentItemShowing;
-
- private SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode,
- boolean isKbMode, ArrayList<Integer> typedTimes,
- int currentItemShowing) {
+
+ private SavedState(Parcelable superState, int hour, int minute) {
super(superState);
mHour = hour;
mMinute = minute;
- mIs24HourMode = is24HourMode;
- mInKbMode = isKbMode;
- mTypedTimes = typedTimes;
- mCurrentItemShowing = currentItemShowing;
}
private SavedState(Parcel in) {
super(in);
mHour = in.readInt();
mMinute = in.readInt();
- mIs24HourMode = (in.readInt() == 1);
- mInKbMode = (in.readInt() == 1);
- mTypedTimes = in.readArrayList(getClass().getClassLoader());
- mCurrentItemShowing = in.readInt();
}
public int getHour() {
@@ -553,31 +582,11 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im
return mMinute;
}
- public boolean is24HourMode() {
- return mIs24HourMode;
- }
-
- public boolean inKbMode() {
- return mInKbMode;
- }
-
- public ArrayList<Integer> getTypesTimes() {
- return mTypedTimes;
- }
-
- public int getCurrentItemShowing() {
- return mCurrentItemShowing;
- }
-
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mHour);
dest.writeInt(mMinute);
- dest.writeInt(mIs24HourMode ? 1 : 0);
- dest.writeInt(mInKbMode ? 1 : 0);
- dest.writeList(mTypedTimes);
- dest.writeInt(mCurrentItemShowing);
}
@SuppressWarnings({"unused", "hiding"})
@@ -592,667 +601,11 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im
};
}
- private void tryVibrate() {
- mDelegator.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
- }
-
- private void updateAmPmDisplay(int amOrPm) {
- if (amOrPm == AM) {
- mAmPmTextView.setText(mAmText);
- mRadialTimePickerView.announceForAccessibility(mAmText);
- } else if (amOrPm == PM){
- mAmPmTextView.setText(mPmText);
- mRadialTimePickerView.announceForAccessibility(mPmText);
- } else {
- mAmPmTextView.setText(mDoublePlaceholderText);
- }
- }
-
- /**
- * Called by the picker for updating the header display.
- */
- @Override
- public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) {
- if (pickerIndex == HOUR_INDEX) {
- updateHeaderHour(newValue, false);
- String announcement = String.format("%d", newValue);
- if (mAllowAutoAdvance && autoAdvance) {
- setCurrentItemShowing(MINUTE_INDEX, true, false);
- announcement += ". " + mSelectMinutes;
- } else {
- mRadialTimePickerView.setContentDescription(
- mHourPickerDescription + ": " + newValue);
- }
-
- mRadialTimePickerView.announceForAccessibility(announcement);
- } else if (pickerIndex == MINUTE_INDEX){
- updateHeaderMinute(newValue);
- mRadialTimePickerView.setContentDescription(mMinutePickerDescription + ": " + newValue);
- } else if (pickerIndex == AMPM_INDEX) {
- updateAmPmDisplay(newValue);
- } else if (pickerIndex == ENABLE_PICKER_INDEX) {
- if (!isTypedTimeFullyLegal()) {
- mTypedTimes.clear();
- }
- finishKbMode();
- }
- }
-
- private void updateHeaderHour(int value, boolean announce) {
- final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
- (mIs24HourView) ? "Hm" : "hm");
- final int lengthPattern = bestDateTimePattern.length();
- boolean hourWithTwoDigit = false;
- char hourFormat = '\0';
- // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save
- // the hour format that we found.
- for (int i = 0; i < lengthPattern; i++) {
- final char c = bestDateTimePattern.charAt(i);
- if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
- hourFormat = c;
- if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
- hourWithTwoDigit = true;
- }
- break;
- }
- }
- final String format;
- if (hourWithTwoDigit) {
- format = "%02d";
- } else {
- format = "%d";
- }
- if (mIs24HourView) {
- // 'k' means 1-24 hour
- if (hourFormat == 'k' && value == 0) {
- value = 24;
- }
- } else {
- // 'K' means 0-11 hour
- value = modulo12(value, hourFormat == 'K');
- }
- CharSequence text = String.format(format, value);
- mHourView.setText(text);
- if (announce) {
- mRadialTimePickerView.announceForAccessibility(text);
- }
- }
-
- private static int modulo12(int n, boolean startWithZero) {
- int value = n % 12;
- if (value == 0 && !startWithZero) {
- value = 12;
- }
- return value;
- }
-
- /**
- * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":".
- *
- * See http://unicode.org/cldr/trac/browser/trunk/common/main
- *
- * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the
- * separator as the character which is just after the hour marker in the returned pattern.
- */
- private void updateHeaderSeparator() {
- final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
- (mIs24HourView) ? "Hm" : "hm");
- final String separatorText;
- // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats
- final char[] hourFormats = {'H', 'h', 'K', 'k'};
- int hIndex = lastIndexOfAny(bestDateTimePattern, hourFormats);
- if (hIndex == -1) {
- // Default case
- separatorText = ":";
- } else {
- separatorText = Character.toString(bestDateTimePattern.charAt(hIndex + 1));
- }
- mSeparatorView.setText(separatorText);
- }
-
- static private int lastIndexOfAny(String str, char[] any) {
- final int lengthAny = any.length;
- if (lengthAny > 0) {
- for (int i = str.length() - 1; i >= 0; i--) {
- char c = str.charAt(i);
- for (int j = 0; j < lengthAny; j++) {
- if (c == any[j]) {
- return i;
- }
- }
- }
- }
- return -1;
- }
-
- private void updateHeaderMinute(int value) {
- if (value == 60) {
- value = 0;
- }
- CharSequence text = String.format(mCurrentLocale, "%02d", value);
- mRadialTimePickerView.announceForAccessibility(text);
- mMinuteView.setText(text);
- }
-
- /**
- * Show either Hours or Minutes.
- */
- private void setCurrentItemShowing(int index, boolean animateCircle, boolean announce) {
- mRadialTimePickerView.setCurrentItemShowing(index, animateCircle);
-
- if (index == HOUR_INDEX) {
- int hours = mRadialTimePickerView.getCurrentHour();
- if (!mIs24HourView) {
- hours = hours % 12;
- }
- mRadialTimePickerView.setContentDescription(mHourPickerDescription + ": " + hours);
- if (announce) {
- mRadialTimePickerView.announceForAccessibility(mSelectHours);
- }
- } else {
- int minutes = mRadialTimePickerView.getCurrentMinute();
- mRadialTimePickerView.setContentDescription(mMinutePickerDescription + ": " + minutes);
- if (announce) {
- mRadialTimePickerView.announceForAccessibility(mSelectMinutes);
- }
- }
-
- mHourView.setSelected(index == HOUR_INDEX);
- mMinuteView.setSelected(index == MINUTE_INDEX);
- }
-
- /**
- * For keyboard mode, processes key events.
- *
- * @param keyCode the pressed key.
- *
- * @return true if the key was successfully processed, false otherwise.
- */
- private boolean processKeyUp(int keyCode) {
- if (keyCode == KeyEvent.KEYCODE_DEL) {
- if (mInKbMode) {
- if (!mTypedTimes.isEmpty()) {
- int deleted = deleteLastTypedKey();
- String deletedKeyStr;
- if (deleted == getAmOrPmKeyCode(AM)) {
- deletedKeyStr = mAmText;
- } else if (deleted == getAmOrPmKeyCode(PM)) {
- deletedKeyStr = mPmText;
- } else {
- deletedKeyStr = String.format("%d", getValFromKeyCode(deleted));
- }
- mRadialTimePickerView.announceForAccessibility(
- String.format(mDeletedKeyFormat, deletedKeyStr));
- updateDisplay(true);
- }
- }
- } else if (keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1
- || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3
- || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5
- || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
- || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9
- || (!mIs24HourView &&
- (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) {
- if (!mInKbMode) {
- if (mRadialTimePickerView == null) {
- // Something's wrong, because time picker should definitely not be null.
- Log.e(TAG, "Unable to initiate keyboard mode, TimePicker was null.");
- return true;
- }
- mTypedTimes.clear();
- tryStartingKbMode(keyCode);
- return true;
- }
- // We're already in keyboard mode.
- if (addKeyIfLegal(keyCode)) {
- updateDisplay(false);
- }
- return true;
- }
- return false;
- }
-
- /**
- * Try to start keyboard mode with the specified key.
- *
- * @param keyCode The key to use as the first press. Keyboard mode will not be started if the
- * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting
- * key.
- */
- private void tryStartingKbMode(int keyCode) {
- if (keyCode == -1 || addKeyIfLegal(keyCode)) {
- mInKbMode = true;
- onValidationChanged(false);
- updateDisplay(false);
- mRadialTimePickerView.setInputEnabled(false);
- }
- }
-
- private boolean addKeyIfLegal(int keyCode) {
- // If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode,
- // we'll need to see if AM/PM have been typed.
- if ((mIs24HourView && mTypedTimes.size() == 4) ||
- (!mIs24HourView && isTypedTimeFullyLegal())) {
- return false;
- }
-
- mTypedTimes.add(keyCode);
- if (!isTypedTimeLegalSoFar()) {
- deleteLastTypedKey();
- return false;
- }
-
- int val = getValFromKeyCode(keyCode);
- mRadialTimePickerView.announceForAccessibility(String.format("%d", val));
- // Automatically fill in 0's if AM or PM was legally entered.
- if (isTypedTimeFullyLegal()) {
- if (!mIs24HourView && mTypedTimes.size() <= 3) {
- mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
- mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
- }
- onValidationChanged(true);
- }
-
- return true;
+ public static String[] getAmPmStrings(Context context) {
+ String[] result = new String[2];
+ LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale);
+ result[0] = d.amPm[0].length() > 2 ? d.narrowAm : d.amPm[0];
+ result[1] = d.amPm[1].length() > 2 ? d.narrowPm : d.amPm[1];
+ return result;
}
-
- /**
- * Traverse the tree to see if the keys that have been typed so far are legal as is,
- * or may become legal as more keys are typed (excluding backspace).
- */
- private boolean isTypedTimeLegalSoFar() {
- Node node = mLegalTimesTree;
- for (int keyCode : mTypedTimes) {
- node = node.canReach(keyCode);
- if (node == null) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Check if the time that has been typed so far is completely legal, as is.
- */
- private boolean isTypedTimeFullyLegal() {
- if (mIs24HourView) {
- // For 24-hour mode, the time is legal if the hours and minutes are each legal. Note:
- // getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode.
- int[] values = getEnteredTime(null);
- return (values[0] >= 0 && values[1] >= 0 && values[1] < 60);
- } else {
- // For AM/PM mode, the time is legal if it contains an AM or PM, as those can only be
- // legally added at specific times based on the tree's algorithm.
- return (mTypedTimes.contains(getAmOrPmKeyCode(AM)) ||
- mTypedTimes.contains(getAmOrPmKeyCode(PM)));
- }
- }
-
- private int deleteLastTypedKey() {
- int deleted = mTypedTimes.remove(mTypedTimes.size() - 1);
- if (!isTypedTimeFullyLegal()) {
- onValidationChanged(false);
- }
- return deleted;
- }
-
- /**
- * Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time.
- */
- private void finishKbMode() {
- mInKbMode = false;
- if (!mTypedTimes.isEmpty()) {
- int values[] = getEnteredTime(null);
- mRadialTimePickerView.setCurrentHour(values[0]);
- mRadialTimePickerView.setCurrentMinute(values[1]);
- if (!mIs24HourView) {
- mRadialTimePickerView.setAmOrPm(values[2]);
- }
- mTypedTimes.clear();
- }
- updateDisplay(false);
- mRadialTimePickerView.setInputEnabled(true);
- }
-
- /**
- * Update the hours, minutes, and AM/PM displays with the typed times. If the typedTimes is
- * empty, either show an empty display (filled with the placeholder text), or update from the
- * timepicker's values.
- *
- * @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text.
- * Otherwise, revert to the timepicker's values.
- */
- private void updateDisplay(boolean allowEmptyDisplay) {
- if (!allowEmptyDisplay && mTypedTimes.isEmpty()) {
- int hour = mRadialTimePickerView.getCurrentHour();
- int minute = mRadialTimePickerView.getCurrentMinute();
- updateHeaderHour(hour, true);
- updateHeaderMinute(minute);
- if (!mIs24HourView) {
- updateAmPmDisplay(hour < 12 ? AM : PM);
- }
- setCurrentItemShowing(mRadialTimePickerView.getCurrentItemShowing(), true, true);
- onValidationChanged(true);
- } else {
- boolean[] enteredZeros = {false, false};
- int[] values = getEnteredTime(enteredZeros);
- String hourFormat = enteredZeros[0] ? "%02d" : "%2d";
- String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d";
- String hourStr = (values[0] == -1) ? mDoublePlaceholderText :
- String.format(hourFormat, values[0]).replace(' ', mPlaceholderText);
- String minuteStr = (values[1] == -1) ? mDoublePlaceholderText :
- String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
- mHourView.setText(hourStr);
- mHourView.setSelected(false);
- mMinuteView.setText(minuteStr);
- mMinuteView.setSelected(false);
- if (!mIs24HourView) {
- updateAmPmDisplay(values[2]);
- }
- }
- }
-
- private int getValFromKeyCode(int keyCode) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_0:
- return 0;
- case KeyEvent.KEYCODE_1:
- return 1;
- case KeyEvent.KEYCODE_2:
- return 2;
- case KeyEvent.KEYCODE_3:
- return 3;
- case KeyEvent.KEYCODE_4:
- return 4;
- case KeyEvent.KEYCODE_5:
- return 5;
- case KeyEvent.KEYCODE_6:
- return 6;
- case KeyEvent.KEYCODE_7:
- return 7;
- case KeyEvent.KEYCODE_8:
- return 8;
- case KeyEvent.KEYCODE_9:
- return 9;
- default:
- return -1;
- }
- }
-
- /**
- * Get the currently-entered time, as integer values of the hours and minutes typed.
- *
- * @param enteredZeros A size-2 boolean array, which the caller should initialize, and which
- * may then be used for the caller to know whether zeros had been explicitly entered as either
- * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's.
- *
- * @return A size-3 int array. The first value will be the hours, the second value will be the
- * minutes, and the third will be either AM or PM.
- */
- private int[] getEnteredTime(boolean[] enteredZeros) {
- int amOrPm = -1;
- int startIndex = 1;
- if (!mIs24HourView && isTypedTimeFullyLegal()) {
- int keyCode = mTypedTimes.get(mTypedTimes.size() - 1);
- if (keyCode == getAmOrPmKeyCode(AM)) {
- amOrPm = AM;
- } else if (keyCode == getAmOrPmKeyCode(PM)){
- amOrPm = PM;
- }
- startIndex = 2;
- }
- int minute = -1;
- int hour = -1;
- for (int i = startIndex; i <= mTypedTimes.size(); i++) {
- int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i));
- if (i == startIndex) {
- minute = val;
- } else if (i == startIndex+1) {
- minute += 10 * val;
- if (enteredZeros != null && val == 0) {
- enteredZeros[1] = true;
- }
- } else if (i == startIndex+2) {
- hour = val;
- } else if (i == startIndex+3) {
- hour += 10 * val;
- if (enteredZeros != null && val == 0) {
- enteredZeros[0] = true;
- }
- }
- }
-
- return new int[] { hour, minute, amOrPm };
- }
-
- /**
- * Get the keycode value for AM and PM in the current language.
- */
- private int getAmOrPmKeyCode(int amOrPm) {
- // Cache the codes.
- if (mAmKeyCode == -1 || mPmKeyCode == -1) {
- // Find the first character in the AM/PM text that is unique.
- KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
- char amChar;
- char pmChar;
- for (int i = 0; i < Math.max(mAmText.length(), mPmText.length()); i++) {
- amChar = mAmText.toLowerCase(mCurrentLocale).charAt(i);
- pmChar = mPmText.toLowerCase(mCurrentLocale).charAt(i);
- if (amChar != pmChar) {
- KeyEvent[] events = kcm.getEvents(new char[]{amChar, pmChar});
- // There should be 4 events: a down and up for both AM and PM.
- if (events != null && events.length == 4) {
- mAmKeyCode = events[0].getKeyCode();
- mPmKeyCode = events[2].getKeyCode();
- } else {
- Log.e(TAG, "Unable to find keycodes for AM and PM.");
- }
- break;
- }
- }
- }
- if (amOrPm == AM) {
- return mAmKeyCode;
- } else if (amOrPm == PM) {
- return mPmKeyCode;
- }
-
- return -1;
- }
-
- /**
- * Create a tree for deciding what keys can legally be typed.
- */
- private void generateLegalTimesTree() {
- // Create a quick cache of numbers to their keycodes.
- final int k0 = KeyEvent.KEYCODE_0;
- final int k1 = KeyEvent.KEYCODE_1;
- final int k2 = KeyEvent.KEYCODE_2;
- final int k3 = KeyEvent.KEYCODE_3;
- final int k4 = KeyEvent.KEYCODE_4;
- final int k5 = KeyEvent.KEYCODE_5;
- final int k6 = KeyEvent.KEYCODE_6;
- final int k7 = KeyEvent.KEYCODE_7;
- final int k8 = KeyEvent.KEYCODE_8;
- final int k9 = KeyEvent.KEYCODE_9;
-
- // The root of the tree doesn't contain any numbers.
- mLegalTimesTree = new Node();
- if (mIs24HourView) {
- // We'll be re-using these nodes, so we'll save them.
- Node minuteFirstDigit = new Node(k0, k1, k2, k3, k4, k5);
- Node minuteSecondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
- // The first digit must be followed by the second digit.
- minuteFirstDigit.addChild(minuteSecondDigit);
-
- // The first digit may be 0-1.
- Node firstDigit = new Node(k0, k1);
- mLegalTimesTree.addChild(firstDigit);
-
- // When the first digit is 0-1, the second digit may be 0-5.
- Node secondDigit = new Node(k0, k1, k2, k3, k4, k5);
- firstDigit.addChild(secondDigit);
- // We may now be followed by the first minute digit. E.g. 00:09, 15:58.
- secondDigit.addChild(minuteFirstDigit);
-
- // When the first digit is 0-1, and the second digit is 0-5, the third digit may be 6-9.
- Node thirdDigit = new Node(k6, k7, k8, k9);
- // The time must now be finished. E.g. 0:55, 1:08.
- secondDigit.addChild(thirdDigit);
-
- // When the first digit is 0-1, the second digit may be 6-9.
- secondDigit = new Node(k6, k7, k8, k9);
- firstDigit.addChild(secondDigit);
- // We must now be followed by the first minute digit. E.g. 06:50, 18:20.
- secondDigit.addChild(minuteFirstDigit);
-
- // The first digit may be 2.
- firstDigit = new Node(k2);
- mLegalTimesTree.addChild(firstDigit);
-
- // When the first digit is 2, the second digit may be 0-3.
- secondDigit = new Node(k0, k1, k2, k3);
- firstDigit.addChild(secondDigit);
- // We must now be followed by the first minute digit. E.g. 20:50, 23:09.
- secondDigit.addChild(minuteFirstDigit);
-
- // When the first digit is 2, the second digit may be 4-5.
- secondDigit = new Node(k4, k5);
- firstDigit.addChild(secondDigit);
- // We must now be followd by the last minute digit. E.g. 2:40, 2:53.
- secondDigit.addChild(minuteSecondDigit);
-
- // The first digit may be 3-9.
- firstDigit = new Node(k3, k4, k5, k6, k7, k8, k9);
- mLegalTimesTree.addChild(firstDigit);
- // We must now be followed by the first minute digit. E.g. 3:57, 8:12.
- firstDigit.addChild(minuteFirstDigit);
- } else {
- // We'll need to use the AM/PM node a lot.
- // Set up AM and PM to respond to "a" and "p".
- Node ampm = new Node(getAmOrPmKeyCode(AM), getAmOrPmKeyCode(PM));
-
- // The first hour digit may be 1.
- Node firstDigit = new Node(k1);
- mLegalTimesTree.addChild(firstDigit);
- // We'll allow quick input of on-the-hour times. E.g. 1pm.
- firstDigit.addChild(ampm);
-
- // When the first digit is 1, the second digit may be 0-2.
- Node secondDigit = new Node(k0, k1, k2);
- firstDigit.addChild(secondDigit);
- // Also for quick input of on-the-hour times. E.g. 10pm, 12am.
- secondDigit.addChild(ampm);
-
- // When the first digit is 1, and the second digit is 0-2, the third digit may be 0-5.
- Node thirdDigit = new Node(k0, k1, k2, k3, k4, k5);
- secondDigit.addChild(thirdDigit);
- // The time may be finished now. E.g. 1:02pm, 1:25am.
- thirdDigit.addChild(ampm);
-
- // When the first digit is 1, the second digit is 0-2, and the third digit is 0-5,
- // the fourth digit may be 0-9.
- Node fourthDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
- thirdDigit.addChild(fourthDigit);
- // The time must be finished now. E.g. 10:49am, 12:40pm.
- fourthDigit.addChild(ampm);
-
- // When the first digit is 1, and the second digit is 0-2, the third digit may be 6-9.
- thirdDigit = new Node(k6, k7, k8, k9);
- secondDigit.addChild(thirdDigit);
- // The time must be finished now. E.g. 1:08am, 1:26pm.
- thirdDigit.addChild(ampm);
-
- // When the first digit is 1, the second digit may be 3-5.
- secondDigit = new Node(k3, k4, k5);
- firstDigit.addChild(secondDigit);
-
- // When the first digit is 1, and the second digit is 3-5, the third digit may be 0-9.
- thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
- secondDigit.addChild(thirdDigit);
- // The time must be finished now. E.g. 1:39am, 1:50pm.
- thirdDigit.addChild(ampm);
-
- // The hour digit may be 2-9.
- firstDigit = new Node(k2, k3, k4, k5, k6, k7, k8, k9);
- mLegalTimesTree.addChild(firstDigit);
- // We'll allow quick input of on-the-hour-times. E.g. 2am, 5pm.
- firstDigit.addChild(ampm);
-
- // When the first digit is 2-9, the second digit may be 0-5.
- secondDigit = new Node(k0, k1, k2, k3, k4, k5);
- firstDigit.addChild(secondDigit);
-
- // When the first digit is 2-9, and the second digit is 0-5, the third digit may be 0-9.
- thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
- secondDigit.addChild(thirdDigit);
- // The time must be finished now. E.g. 2:57am, 9:30pm.
- thirdDigit.addChild(ampm);
- }
- }
-
- /**
- * Simple node class to be used for traversal to check for legal times.
- * mLegalKeys represents the keys that can be typed to get to the node.
- * mChildren are the children that can be reached from this node.
- */
- private class Node {
- private int[] mLegalKeys;
- private ArrayList<Node> mChildren;
-
- public Node(int... legalKeys) {
- mLegalKeys = legalKeys;
- mChildren = new ArrayList<Node>();
- }
-
- public void addChild(Node child) {
- mChildren.add(child);
- }
-
- public boolean containsKey(int key) {
- for (int i = 0; i < mLegalKeys.length; i++) {
- if (mLegalKeys[i] == key) {
- return true;
- }
- }
- return false;
- }
-
- public Node canReach(int key) {
- if (mChildren == null) {
- return null;
- }
- for (Node child : mChildren) {
- if (child.containsKey(key)) {
- return child;
- }
- }
- return null;
- }
- }
-
- private final View.OnKeyListener mKeyListener = new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_UP) {
- return processKeyUp(keyCode);
- }
- return false;
- }
- };
-
- private final View.OnFocusChangeListener mFocusListener = new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (!hasFocus && mInKbMode && isTypedTimeFullyLegal()) {
- finishKbMode();
-
- if (mOnTimeChangedListener != null) {
- mOnTimeChangedListener.onTimeChanged(mDelegator,
- mRadialTimePickerView.getCurrentHour(),
- mRadialTimePickerView.getCurrentMinute());
- }
- }
- }
- };
}
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index dd165ae..be4cdc1 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -235,6 +235,14 @@ public class Toast {
public int getYOffset() {
return mTN.mY;
}
+
+ /**
+ * Gets the LayoutParams for the Toast window.
+ * @hide
+ */
+ public WindowManager.LayoutParams getWindowParams() {
+ return mTN.mParams;
+ }
/**
* Make a standard toast that just contains a text view.
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 1ce19ce..c5325c4 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.app.ActionBar;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -66,7 +67,9 @@ import java.util.List;
* <li><em>A navigation button.</em> This may be an Up arrow, navigation menu toggle, close,
* collapse, done or another glyph of the app's choosing. This button should always be used
* to access other navigational destinations within the container of the Toolbar and
- * its signified content or otherwise leave the current context signified by the Toolbar.</li>
+ * its signified content or otherwise leave the current context signified by the Toolbar.
+ * The navigation button is vertically aligned within the Toolbar's
+ * {@link android.R.styleable#View_minHeight minimum height}, if set.</li>
* <li><em>A branded logo image.</em> This may extend to the height of the bar and can be
* arbitrarily wide.</li>
* <li><em>A title and subtitle.</em> The title should be a signpost for the Toolbar's current
@@ -82,8 +85,9 @@ import java.util.List;
* <li><em>An {@link ActionMenuView action menu}.</em> The menu of actions will pin to the
* end of the Toolbar offering a few
* <a href="http://developer.android.com/design/patterns/actionbar.html#ActionButtons">
- * frequent, important or typical</a> actions along with an optional overflow menu for
- * additional actions.</li>
+ * frequent, important or typical</a> actions along with an optional overflow menu for
+ * additional actions. Action buttons are vertically aligned within the Toolbar's
+ * {@link android.R.styleable#View_minHeight minimum height}, if set.</li>
* </ul>
* </p>
*
@@ -101,6 +105,7 @@ public class Toolbar extends ViewGroup {
private ImageView mLogoView;
private Drawable mCollapseIcon;
+ private CharSequence mCollapseDescription;
private ImageButton mCollapseButtonView;
View mExpandedActionView;
@@ -235,6 +240,7 @@ public class Toolbar extends ViewGroup {
}
mCollapseIcon = a.getDrawable(R.styleable.Toolbar_collapseIcon);
+ mCollapseDescription = a.getText(R.styleable.Toolbar_collapseContentDescription);
final CharSequence title = a.getText(R.styleable.Toolbar_title);
if (!TextUtils.isEmpty(title)) {
@@ -995,6 +1001,7 @@ public class Toolbar extends ViewGroup {
if (mCollapseButtonView == null) {
mCollapseButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle);
mCollapseButtonView.setImageDrawable(mCollapseIcon);
+ mCollapseButtonView.setContentDescription(mCollapseDescription);
final LayoutParams lp = generateDefaultLayoutParams();
lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
lp.mViewType = LayoutParams.EXPANDED;
@@ -1746,6 +1753,17 @@ public class Toolbar extends ViewGroup {
}
/**
+ * Accessor to enable LayoutLib to get ActionMenuPresenter directly.
+ */
+ ActionMenuPresenter getOuterActionMenuPresenter() {
+ return mOuterActionMenuPresenter;
+ }
+
+ Context getPopupContext() {
+ return mPopupContext;
+ }
+
+ /**
* 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/VideoView.java b/core/java/android/widget/VideoView.java
index 572cca2..47644f9 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -311,6 +311,8 @@ public class VideoView extends SurfaceView
mMediaPlayer = null;
mCurrentState = STATE_IDLE;
mTargetState = STATE_IDLE;
+ AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ am.abandonAudioFocus(null);
}
}
@@ -319,12 +321,13 @@ public class VideoView extends SurfaceView
// not ready for playback just yet, will try again later
return;
}
- AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
-
// we shouldn't clear the target state, because somebody might have
// called start() previously
release(false);
+
+ AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
+
try {
mMediaPlayer = new MediaPlayer();
// TODO: create SubtitleController in MediaPlayer, but we need
@@ -650,6 +653,8 @@ public class VideoView extends SurfaceView
if (cleartargetstate) {
mTargetState = STATE_IDLE;
}
+ AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ am.abandonAudioFocus(null);
}
}
diff --git a/core/java/android/widget/YearPickerView.java b/core/java/android/widget/YearPickerView.java
index 2bf07f9..24ed7ce 100644
--- a/core/java/android/widget/YearPickerView.java
+++ b/core/java/android/widget/YearPickerView.java
@@ -18,7 +18,6 @@ package android.widget;
import android.content.Context;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -33,10 +32,15 @@ import com.android.internal.R;
*/
class YearPickerView extends ListView implements AdapterView.OnItemClickListener,
OnDateChangedListener {
+ private final Calendar mMinDate = Calendar.getInstance();
+ private final Calendar mMaxDate = Calendar.getInstance();
+
+ private final YearAdapter mAdapter;
+ private final int mViewSize;
+ private final int mChildSize;
+
private DatePickerController mController;
- private YearAdapter mAdapter;
- private int mViewSize;
- private int mChildSize;
+
private int mSelectedPosition = -1;
private int mYearSelectedCircleColor;
@@ -72,15 +76,23 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener
setOnItemClickListener(this);
setDividerHeight(0);
+
+ mAdapter = new YearAdapter(getContext(), R.layout.year_label_text_view);
+ setAdapter(mAdapter);
+ }
+
+ public void setRange(Calendar min, Calendar max) {
+ mMinDate.setTimeInMillis(min.getTimeInMillis());
+ mMaxDate.setTimeInMillis(max.getTimeInMillis());
+
+ updateAdapterData();
}
public void init(DatePickerController controller) {
mController = controller;
mController.registerOnDateChangedListener(this);
- mAdapter = new YearAdapter(getContext(), R.layout.year_label_text_view);
updateAdapterData();
- setAdapter(mAdapter);
onDateChanged();
}
@@ -98,8 +110,9 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener
private void updateAdapterData() {
mAdapter.clear();
- final int maxYear = mController.getMaxYear();
- for (int year = mController.getMinYear(); year <= maxYear; year++) {
+
+ final int maxYear = mMaxDate.get(Calendar.YEAR);
+ for (int year = mMinDate.get(Calendar.YEAR); year <= maxYear; year++) {
mAdapter.add(year);
}
}
@@ -173,12 +186,13 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener
updateAdapterData();
mAdapter.notifyDataSetChanged();
postSetSelectionCentered(
- mController.getSelectedDay().get(Calendar.YEAR) - mController.getMinYear());
+ mController.getSelectedDay().get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR));
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
+
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
event.setFromIndex(0);
event.setToIndex(0);